0515. Array 與 List 容器

Containers - Array and List
可變容器 Array (陣列, 數組)與不可變有序容器 List (序列, 列表)

在 Scala 中充分利用其 List 函數和遞歸的特性, 足以完全取代 while 和 for 的角色並且更加強大. - WisdomFish.ORG

Scala 為 Java 泛型容器提供了增強型的類型推斷與類型安全性.


Array VS. List


  • scala.Array 是一個所有內容物件都共享相同類型的"可變"序列
    • 比方說 Array[String] 僅包含 String。儘管實例化之後你無法改變 Array 的長度,它的元素值卻是可變的。
  • scala.List 是共享相同類型的"不可變物件"序列
    • 和 陣列一樣,List[String] 包含的僅僅是 String。
    • Scala 的 List,scala.List,不同於 Java 的 java.util.List,確保它是不可變的(而 Java 的 List可變)。
    • 更一般性的說法,Scala 的 List 是設計給函數式風格編程用的。
    • List 是不可變的,他們表現得有些像 Java 的 String:當你在一個 List 上調用方法時,似乎這個名字指代的 List 看上去被改變了,而實際上它只是用新的值創建了一個 List 並返回

scala> val x = Array[Int](1, 2, 3)
x: Array[Int] = Array(1, 2, 3)


YouTube Video




Java SE API 對 List 的說明


  1. List 是有序的集合(Collection)。使用者可以對 List 中每個元素的插入位置進行精確地控制。
  2. 使用者可以根據元素的整數索引(在 List 中的位置)存取元素,並搜尋列表中的元素。
  3. 與 Set 不同, List 通常允許重複的元素。更 確切地講, List 通常允許滿足 e1.equals(e2) 的元素對 e1 和 e2,並且如果 List 本身允許 null 元素的話,通常它們允許多個 null 元素。難免有人希望通過在使用者嘗試插入重複元素時拋出運行時異常的方法來禁止重複的 List ,但我們希望這種用法越少越好。
  4. 在 List 中搜尋指定物件的方法。從性能的觀點來看,應該小心使用這些方法。在很多實作中,它們將執行高開銷的線性搜尋。


For Java Developer


相同處

  1. List


不同處

  1. Scala 的 List 是不可變的(Immutable)容器, 而 java.util.List 是可變的(mutable). 更重要的是, Scala List 被設計授與作為 函數風格(functional style) 的編程設計能力.
  2. 不能使用 new 來創建 Scala List, 因為它是被定義在 scala.List Singleton 物件上的 factory 方法.
  3. ::: 連3冒號符 可以用來鏈結二個 List 產生新的 List 容器內容.
    1. 「:::」的方法實現疊加功能。
  4. :: 連2冒號符(cons) 用以將元素加至 List 中.
    1. 最常用的操作符是發音為「cons」的『::』。Cons把一個新元素組合到已有List的最前端,然後返回結果List。
  5. List 沒有提供 append 操作,因為隨著列表變長 append 的耗時將呈線性增長,而使用::做前綴則僅花費常量時間



References





scala.collection.immutable.List


class List[+A] extends LinearSeq[A] with Product with GenericTraversableTemplate[A, List[A][A]] with LinearSeqOptimized[A, List[A]]

A class for immutable linked lists representing ordered collections of elements of type .
This class comes with two implementing case classes scala.Nil and scala.:: that implement the abstract members isEmpty, head and tail. A

the type of the list's elements
attributes: sealed abstract
go to: companion
known subclasses: ::, Nil



發音為「cons」的『::』

表達式「1 :: twoThree」中,::是它右操作數,列表twoThree,的方法。你或許會疑惑::方法的關聯性上有什麼東西搞錯了,不過這只是一個簡單的需記住的規則:如果一個方法被用作操作符標註,如a * b,那麼方法被左操作數調用,就像a.*(b)——除非方法名以冒號結尾。這種情況下,方法被右操作數調用。因此,1 :: twoThree裡,::方法被twoThree調用,傳入1,像這樣:twoThree.::(1)。

由於定義空類別的捷徑是Nil, 所以一種初始化新List的方法是把所有元素用cons操作符串起來,Nil作為最後一個元素。

scala> val list4 = Nil
list4: Nil.type = List()



Example


    val symbol01: List[Boolean] = yangValue::yangValue::Nil
    val symbol02 = yangValue::yinValue::Nil
   
    val symbol03: List[Boolean] = List[Boolean](yinValue, yinValue)
    val symbol04 = List[Boolean](yinValue, yangValue)



List 常用的操作



scala> val a = Nil  
a: scala.collection.immutable.Nil.type = List()

scala> val b = 22::Nil
b: List[Int] = List(22)

scala> val c = 22::a 
c: List[Int] = List(22)

scala> val d = b:::c  
d: List[Int] = List(22, 22)


取得特定值, 索引值從零開始
scala> val list = List[Int](1,2)
list: List[Int] = List(1, 2)
scala> list(1)
res9: Int = 2

















scala.List[+A]


sealed abstract class List[+A]
extends Seq[A] with Product
A class representing an ordered collection of elements of type a. This class comes with two implementing case classes scala.Nil and scala.:: that implement the abstract members isEmpty, head and tail.

類別 A 表現為 a 型別元素的有序集合.



Syntax



scala> val list = List[Int](1,2)
list: List[Int] = List(1, 2)

scala> val list = List(1,2)    
list: List[Int] = List(1, 2)

scala> var list = List(3.3, "Hello")
list: List[Any] = List(3.3, Hello)








Reference







Use Lists and Tuples

One of the big ideas of the functional style of programming is that methods should not have side effects. The only effect of a method should be to compute the value or values that are returned by the method. Some benefits gained when you take this approach are that methods become less entangled, and therefore more reliable and reusable. Another benefit of the functional style in a statically typed language is that everything that goes into and out of a method is checked by a type checker, so logic errors are more likely to manifest themselves as type errors. To apply this functional philosophy to the world of objects, you would make objects immutable. A simple example of an immutable object in Java is String. If you create a String with the value "Hello, ", it will keep that value for the rest of its lifetime. If you later call concat("world!") on that String, it will not add "world!" to itself. Instead, it will create and return a brand new String with the value Hello, world!".

As you've seen, a Scala Array is a mutable sequence of objects that all share the same type. An Array[String] contains only Strings, for example. Although you can't change the length of an Array after it is instantiated, you can change its element values. Thus, Arrays are mutable objects. An immutable, and therefore more functional-oriented, sequence of objects that share the same type is Scala's List. As with Arrays, a List[String] contains only Strings. Scala's List, scala.List, differs from Java's java.util.List type in that Scala Lists are always immutable (whereas Java Lists can be mutable). But more importantly, Scala's List is designed to enable a functional style of programming. Creating a List is easy, you just say:

val oneTwoThree = List(1, 2, 3)

This establishes a new \@val@ named oneTwoThree, which initialized with a new List[Int] with the integer element values 1, 2 and 3. (You don't need to say new List because “List” is defined as a factory method on the scala.List singleton object. More on Scala's singleton object construct in Step 11.) Because Lists are immutable, they behave a bit like Java Strings in that when you call a method on one that might seem by its name to imply the List will be mutated, it instead creates a new List with the new value and returns it. For example, List has a method named ::: that concatenates a passed List and the List on which ::: was invoked. Here's how you use it:

val oneTwo = List(1, 2)
val threeFour = List(3, 4)
val oneTwoThreeFour = oneTwo ::: threeFour
println(oneTwo + " and " + threeFour + " were not mutated.")
println("Thus, " + oneTwoThreeFour + " is a new List.")

Type this code into a new file called listcat.scala and execute it with scala listcat.scala, and you should see:

List(1, 2) and List(3, 4) were not mutated.
Thus, List(1, 2, 3, 4) is a new List.

Enough said.2

Perhaps the most common operator you'll use with Lists is ::, which is pronounced “cons.” Cons prepends a new element to the beginning of an existing List, and returns the resulting List. For example, if you type the following code into a file named consit.scala:

val twoThree = List(2, 3)
val oneTwoThree = 1 :: twoThree
println(oneTwoThree)

And execute it with scala consit.scala, you should see:

List(1, 2, 3)

Given that a shorthand way to specify an empty List is Nil, one way to initialize new Lists is to string together elements with the cons operator, with Nil as the last element. For example, if you type the following code into a file named consinit.scala:

val oneTwoThree = 1 :: 2 :: 3 :: Nil
println(oneTwoThree)

And execute it with scala consinit.scala, you should again see:

List(1, 2, 3)

Scala's List is packed with useful methods, many of which are shown in Table 1.

What it Is What it Does
List() Creates an empty List
Nil Creates an empty List
List("Cool", "tools", "rule") Creates a new List[String] with the three values "Cool", "tools", and "rule"
val thrill = "Will" :: "fill" :: "until" :: Nil Creates a new List[String] with the three values "Will", "fill", and "until"
thrill(2) Returns the 2nd element (zero based) of the thrill List (returns "until")
thrill.count(s => s.length == 4) Counts the number of String elements in thrill that have length 4 (returns 2)
thrill.drop(2) Returns the thrill List without its first 2 elements (returns List("until"))
thrill.dropRight(2) Returns the thrill List without its rightmost 2 elements (returns List("Will"))
thrill.exists(s => s == "until") Determines whether a String element exists in thrill that has the value "until" (returns true)
thrill.filter(s => s.length == 4) Returns a List of all elements, in order, of the thrill List that have length 4 (returns List("Will", "fill"))
thrill.forall(s => s.endsWith("l")) Indicates whether all elements in the thrill List end with the letter "l" (returns true)
thrill.foreach(s => print(s)) Executes the print statement on each of the Strings in the thrill List (prints "Willfilluntil")
thrill.foreach(print) Same as the previous, but more concise (also prints "Willfilluntil")
thrill.head Returns the first element in the thrill List (returns "Will")
thrill.init Returns a List of all but the last element in the thrill List (returns List("Will", "fill"))
thrill.isEmpty Indicates whether the thrill List is empty (returns false)
thrill.last Returns the last element in the thrill List (returns "until")
thrill.length Returns the number of elements in the thrill List (returns 3)
thrill.map(s => s + "y") Returns a List resulting from adding a "y" to each String element in the thrill List (returns List("Willy", "filly", "untily"))
thrill.remove(s => s.length == 4) Returns a List of all elements, in order, of the thrill List except those that have length 4 (returns List("until"))
thrill.reverse Returns a List containing all element of the thrill List in reverse order (returns List("until", "fill", "Will"))
thrill.sort((s, t) => s.charAt(0).toLowerCase < t.charAt(0).toLowerCase) Returns a List containing all element of the thrill List in alphabetical order of the first character lowercased (returns List("fill", "until", "Will"))
thrill.tail Returns the thrill List minus its first element (returns List("fill", "until"))

Table 1. Some List methods and usages.