[Scala] - day 7 : Collections trong Scala [phần 3] - Array và Seq


Arrays

Array không phải là 1 collection, tất nhiên vì nó không nằm trong thư viện “scala.collections” .  Kiểu Array thực sự là dùng lại của Java với 1 vài feature giúp nó có thể được sử dụng như 1 sequence. Scala cung cấp kiểu Array tích hợp trong JVM libraries và Java code , được hỗ trợ như 1 indexed collections.
Một Array có các đặc điểm sau :
  • fixed-size : sau khi tạo xong bạn không thể thay đổi được số lượng element trong Array.
  • giá trị element trong Array có thể thay đổi được.
  • indexed collection : tất nhiên,trong array các element được index từ 0
scala> val colors = Array("red", "green", "blue")
colors: Array[String] = Array(red, green, blue)
scala> colors.map ( (a : String) => println(a) )
red
green
blue
res1: Array[Unit] = Array((), (), ()) 
scala> colors(0) = "do" 
scala> colors.map ( (a : String) => println(a) )
do
green
blue
res3: Array[Unit] = Array((), (), ()) 
scala> colors(3) = "xxx"
java.lang.ArrayIndexOutOfBoundsException: 3
... 33 elided

Seq

Seq là root của tất cả sequences, bao gồm các chuỗi liên tục như List hay được index như là Vector. Là root, Seq tự nó không thể khởi tạo, nhưng bạn có thể sử dụng nó như 1 shortcut để tạo ra 1 List:
scala> val dayLaSeq = Seq('h', 'o', 'a', 'i')
dayLaSeq: Seq[Char] = List(h, o, a, i)
Mối liên hệ giữa các sequence collections được thể hiện theo bảng sau :

  The sequence collections hierarchy
  • Seq: Là root của tất cả sequences. Shortcut cho List().
  • IndexedSeq: Là root của tất cả  indexed sequences. Shortcut cho Vector().
  • Vector: Là 1 instance của Array hỗ trợ truy cập theo chỉ mục.
  • Range: Là 1 dãy các số nguyên, giúp tạo dữ liệu nhanh.
  • LinearSeq: Là root của tất cả sequences liên tục.
  • List: Là 1 chuỗi liên tục các element.
  • Queue: Là 1 . . . queue :) (vào trước ra trước) FIFO list.
  • Stack: Là 1 . . . stack(vào sau ra trước) LIFO list.
  • Stream: Lazy list, thêm phần tử mới tương đương với access phần tử đó.
  • String: Là 1 chuỗi (tất nhiên) ngoài ra nó còn là 1 collection các ký tự.
Với những thông tin ta có được, có lẽ Stream là khó hiểu nhất ,hãy cùng tìm hiểu sâu hơn về nó.

Stream

Kiểu Stream là 1 lazy collection, được tạo từ 1 hay nhiều elements khởi điểm và 1 hàm đệ quy.Mỗi phần tử của thêm vào stream khi nó được access đến lần đầu tiên, khác với immutable collections nhận 100% gía trị của nó khi nó được khởi tạo.Mỗi phần tử được stream tạo ra được cache lại và lấy lại sau đó, đảm bảo rằng mỗi phần tử được khởi tạo chỉ 1. Stream có thể lớn đến vô cùng ,và điểm cuối cùng của stream là Stream.Empty ,tương tự với List.Nil.
Cấu trúc của stream tương tự list , bao gồm head và tail.Chúng có thể được xây dựng với 1 function trả về stream mới với phần tử head và phần tử tail được tạo bằng cách đệ quy lại chính function này.Bạn có thể sử dụng Stream.cons để xây dựng 1 stream mới với head và tail.
Chúng ta sẽ tìm hiểu kỹ hơn bằng ví dụ :
scala> def inc(i : Int): Stream[Int] = Stream.cons(i, inc(i+1))
inc: (i: Int)Stream[Int]
Ở đây tôi tạo 1 function nhận và 1 số Int và trả về 1 Stream[Int] ,thân chương trình tôi sử dụng cấu trúc Stream.cons, thử xem function này sẽ chạy như nào :
scala> val s = inc(1)
s: Stream[Int] = Stream(1, ?)
Tôi gọi function inc truyền vào số 1 và gán vào biến s, và kết quả thu được là 1 Stream với phần tử đầu là 1 và phần tử sau là chưa xác định, bạn thấy rằng Stream.cons của chúng ta không chạy luôn khi bạn khởi gán nó.Tôi sẽ ép nó phải chạy bằng cách convert nó sang List và gán vào 1 biến khác.
scala> val l = inc(1).toList
java.lang.OutOfMemoryError: GC overhead limit exceeded
   at java.lang.Integer.valueOf(Integer.java:832)
   at scala.runtime.BoxesRunTime.boxToInteger(BoxesRunTime.java:65)
   at .inc(<console>:12)
   at $anonfun$inc$1.apply(<console>:12)
   at $anonfun$inc$1.apply(<console>:12)
   at scala.collection.immutable.Stream$Cons.tail(Stream.scala:1233)
   at scala.collection.immutable.Stream$Cons.tail(Stream.scala:1223)
   at scala.collection.generic.Growable$class.loop$1(Growable.scala:54)
   at scala.collection.generic.Growable$class.$plus$plus$eq(Growable.scala:57)
   at scala.collection.mutable.ListBuffer.$plus$plus$eq(ListBuffer.scala:183)
   at scala.collection.mutable.ListBuffer.$plus$plus$eq(ListBuffer.scala:45)
   at scala.collection.TraversableLike$class.to(TraversableLike.scala:590)
   at scala.collection.AbstractTraversable.to(Traversable.scala:104)
   at scala.collection.TraversableOnce$class.toList(TraversableOnce.scala:294)
   at scala.colection.AbstractTraversable.toList(Traversable.scala:104)
   ... 17 elided
Bạn thấy ngay với với cấu trúc đệ quy của chúng ta cứ tăng dần dẫn đến  OutOfMemoryError và 1 đặc điểm của Stream :  Mỗi phần tử của thêm vào stream khi nó được access đến lần đầu tiên .Vậy làm sao để giới hạn số lần lặp ,có 2 cách, bạn sẽ chặn trong hàm đệ quy của mình hoặc nói với Scala bạn muốn lặp bao nhiêu lần bằn method take :
scala> val streamWith5Elements = inc(1).take(5)
streamWith5Elements: scala.collection.immutable.Stream[Int] = Stream(1, ?) 
scala> streamWith5Elements.toList
res7: List[Int] = List(1, 2, 3, 4, 5) 
scala> streamWith5Elements
res8: scala.collection.immutable.Stream[Int] = Stream(1, 2, 3, 4, 5)
Một cách khác nữa bạn có thể dùng để tạo ra 1 Stream đó là dùng cấu trúc : head #:: tail , tôi sẽ viết lại function inc dùng cấu trúc này :

scala> def inc(head: Int): Stream[Int] = head #:: inc(head+1)
inc: (head: Int)Stream[Int]
Đên giờ tất cả những collection mà chúng ta biết chúng đều là 1 tập giữ 1 hoặc nhiều phần tử, trong Scala chúng ta còn 1 set những collection mà bản thân chúng chỉ chứa 1 không quá 1 phần tử, chúng được gọi là monadic collections, phần cuối cùng của collection chúng ta sẽ cùng tìm hiểu về Monadic Collections.


Comments