列表
使用列表
列表字面量
再简单回顾一下:
val fruit = List("apples", "oranges", "pears") val nums = List(1, 2, 3, 4) val diag3 = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) val empty = List()
注意列表是不可变的。
列表类型
列表是同质化的(homogeneous),所有的成员都有相同的类型,中括号描述成员类型
List[T]
。
val fruit: List[String] = List("apples", "oranges", "pears") val nums: List[Int] = List(1, 2, 3, 4) val diag3: List[List[Int]] = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) val empty: List[Nothing] = List()
Scala里的 不可变 列表类是协变的(covariant)。如果S
是T
的子类,那List[S]
也是List[T]
的子类。
由于Nothing
是所有类的子类,所以List[Nothing]
是所有List[T]
类型的子类:
// List() is also of type List[String]! val xs: List[String] = List()
构造列表
Nil
代表空列表;::
(发音为「cons」),elm::list
把单个元素elm
接在列表list
的前面。
val fruit = "apples" :: ("oranges" :: ("pears" :: Nil)) val nums = 1 :: (2 :: (3 :: (4 :: Nil))) val diag3 = (1 :: (0 :: (0 :: Nil))) :: (0 :: (1 :: (0 :: Nil))) :: (0 :: (0 :: (1 :: Nil))) :: Nil val empty = Nil
由于操作符::
是右结合性,所以:
A :: (B :: C)
相当于:
A :: B :: C
所以前一个例子中很多括号都可以省略:
val nums = 1 :: 2 :: 3 :: 4 :: Nil
列表的基本操作
三个基本操作:head
、tail
、isEmpty
。
val fruit = "apples" :: "oranges" :: "pears" :: Nil val nums = 1 :: 2 :: 3 :: 4 :: Nil val diag3 = (1 :: (0 :: (0 :: Nil))) :: (0 :: (1 :: (0 :: Nil))) :: (0 :: (0 :: (1 :: Nil))) :: Nil val empty = Nil empty.isEmpty // true fruit.isEmpty // flase fruit.head // "apples" fruit.tail.head // "organges" diag3.head // List(1, 0, 0)
head
与tail
只能用在非空列表上,不然抛异常:
scala> Nil.head java.util.NoSuchElementException: head of empty list
一个排序的例子,使用插入排序:对于非空列表x::xs
可以先排序xs
。然后再把x
插入
正确的地方:
def isort(xs: List[Int]): List[Int] = if (xs.isEmpty) Nil else insert(xs.head, isort(xs.tail)) def insert(x: Int, xs: List[Int]): List[Int] = if (xs.isEmpty || x <= xs.head) x :: xs else xs.head :: insert(x, xs.tail)
其他常用操作:
-
添加元素。在头部添加元素:
1 :: 2 :: Nil
-
添加元素到尾部(需要遍历,性能较差):
List(1, 2, 3, 4, 5) :+ 6
-
连接列表,把两个列表连起来:
List(1, 2) ::: List(2, 3)
-
添加集合,把另一个集合里的东西都加进去:
List(1, 2) ++ Set(3, 4, 5)
-
检查相等,两个集合的类型和元素都相同:
List(1, 2) == List(1, 2)
-
去重:
List(1, 2, 3, 4, 5, 4, 3).distinct
-
删除,删除前
n
个元素:List('a', 'b', 'c') drop 2
-
删除,删除后
n
个元素(需要遍历,性能较差):List('a', 'b', 'c') dropRight 2
-
过滤:
List(1, 8, 5, 20, 3, 77, 66) filter (_ > 18)
-
降维,多维列表重组为一维的:
List(List(1, 2, 3), List(4, 5, 6)).flatten
-
分区,按条件分为一个元组中的两个表:
List(1, 2, 3, 4, 5) partition (_ < 3)
-
反序:
List(1, 2, 3).reverse
-
分片:
List(0, 1, 2, 3, 4, 5) slice (1, 3)
-
按条件排序:
List("apple", "to") sortBy (_.size)
-
按默认类型属性排序:
List("apple", "to").sorted
-
切分,按索引切分:
List(2, 3, 5, 7) splitAt 2
-
取前
n
个元素:List(0, 1, 2, 3, 4, 5, 6) take 3
-
取后
n
个元素(需要遍历,性能较差):List(0, 1, 2, 3, 4, 5, 6) takeRight 3
-
压缩两个列表:
List(1, 2) zip List("a", "b")
映射并转换每一个元素:
scala> List("a", "b", "c") map (_.toUpperCase) res13: List[String] = List(A, B, C)
用偏函数转换并采集元素:
scala> List(0, 1, 0).collect({case 0 => "ok"}) res8: List[String] = List(ok, ok)
转换每个元素后,重组为一维的表:
scala> List("milk,tea,coffee") map (_.split(',')) // 不重组到一维 res12: List[Array[String]] = List(Array(milk, tea, coffee)) scala> List("milk,tea,coffee") flatMap (_.split(',')) // 重组到一维 res11: List[String] = List(milk, tea, coffee)
数学归约操作:
-
最大的:
List(1, 3, 2).max
-
最小的:
List(1.1, 3.77, 2).min
-
所有元素求和:
List(1, 2, 3).sum
-
所有元素相乘:
List(1, 2, 3).product
布尔归约操作:
-
所有的元素都要满足每件:
List(24, 17, 32) forall (_ < 18)
-
至少有一个元素都要满足每件:
List(24, 17, 32) exists (_ < 18)
-
是否包含指定元素:
List(1, 2, 3) contains 2
或List(1, 2, 3) contains ((x: Int) => {x > 2})
-
以指定列表开头:
List(1, 2, 3) startsWith List(0)
-
以指定列表结尾:
List(1, 2, 3) endsWith List(2, 3)
通用的列表归约操作:
flod函数:
scala> List(1, 2, 3, 4, 5, 6).fold(0)(_ + _) res4: Int = 21 scala> List(1, 2, 3, 4, 5, 6).foldLeft(0)(_ + _) res5: Int = 21 scala> List(1, 2, 3, 4, 5, 6).foldRight(0)(_ + _) res6: Int = 21
reduce函数:
scala> List(1, 2, 3, 4, 5, 6).reduce(_ + _) res7: Int = 21 scala> List(1, 2, 3, 4, 5, 6).reduceLeft(_ + _) res8: Int = 21 scala> List(1, 2, 3, 4, 5, 6).reduceRight(_ + _) res9: Int = 21
scan函数:
scala> List(1, 2, 3, 4, 5, 6).scan(0)(_ + _) res10: List[Int] = List(0, 1, 3, 6, 10, 15, 21) scala> List(1, 2, 3, 4, 5, 6).scanLeft(0)(_ + _) res11: List[Int] = List(0, 1, 3, 6, 10, 15, 21) scala> List(1, 2, 3, 4, 5, 6).scanRight(0)(_ + _) res12: List[Int] = List(21, 20, 18, 15, 11, 6, 0)
列表模式
简单的模式匹配,在确定长度的情况下取出列表里的元素:
scala> val List(a, b, c) = fruit a: String = apples b: String = oranges c: String = pears
不确定具体长度但知道至少有几个,或是只要取前几个:
scala> val a :: b :: rest = fruit a: String = apples b: String = oranges rest: List[String] = List(pears)
要注意这里的List(...)
和::
并不是之前定义的模式匹配。
实际上List(...)
是将来会在抽取器章节介绍的抽取器模式。
「cos」模式:
x :: xs
是中缀操作符模式的特例,一般中缀表达式p op q
视为p.op(q)
。
但是如果作为模式,其实是被当作构造器模式的op(p,q)
形式。
对应这个构造器模式的类是scala.::
,它可以创建非空列表的类。还有一个List
类的
方法::
用来实例化scala.::
的对象。在将来的「实现列表」章节中会有进一步的描述。
再次用模式匹配的方式来实现前面已经实现过的插入排序法:
def isort(xs: List[Int]): List[Int] = xs match { case List() => List() // empty list case x :: xs1 => insert(x, isort(xs1)) } def insert(x: Int, xs: List[Int]): List[Int] = xs match { case List() => List(x) // empty list case y :: ys => if (x <= y) x :: xs else y :: insert(x, ys) }
List类的一阶方法
这里介绍的方法是List类的方法,所以是在独立的对象上被调用。
连接列表
连接两个列表的操作符是:::
,例如:
scala> List(1, 2) ::: List(3, 4, 5) res0: List[Int] = List(1, 2, 3, 4, 5) scala> List() ::: List(1, 2, 3) res1: List[Int] = List(1, 2, 3) scala> List(1, 2, 3) ::: List(4) res2: List[Int] = List(1, 2, 3, 4)
它也是右结合的:
xs ::: ys ::: zs
相当于:
xs ::: (ys ::: zs)
分治原则
手动实现一个连接列表的append
方法。先用模式匹配把输入的列表拆分为更加简单的样本:
def append[T](xs: List[T], ys: List[T]): List[T] = xs match { case List() => ys case x :: xs1 => x :: append(xs1, ys) }
以上代码的让ys
操持完整而xs
被一步步拆分并放到ys
前面,所以把注意集中到xs
的
模式匹配上。
再通过递归调用层层套用剩下的元素,通过添加单个元素的方法::
连接列表。
列表长度
scala> List(1, 2, 3).length res3: Int = 3
length
方法要遍历整个列表来取得长度,所以判断是否为空一般用isEmpty
而不用length
。
取头和尾
head
取头,tail
取的是除了第一个元素外剩下列表。这两个方法的运行时间是常量。
last
取尾,init
取最后一个以外的列表。这两个方法会遍历整个列表。
scala> val abcde = List('a', 'b', 'c', 'd', 'e') abcde: List[Char] = List(a, b, c, d, e) scala> abcde.last res4: Char = e scala> abcde.init res5: List[Char] = List(a, b, c, d)
对于空列表会抛异常
scala> List().init java.lang.UnsupportedOperationException: Nil.init at scala.List.init(List.scala:544) at ... scala> List().last java.util.NoSuchElementException: Nil.last at scala.List.last(List.scala:563) at ...
反转列表
reverse
是创建了一个新列表:
scala> abcde.reverse res6: List[Char] = List(e, d, c, b, a) scala> abcde res7: List[Char] = List(a, b, c, d, e)
一些简单的规律:
xs.reverse.reverse equals xs xs.reverse.init equals xs.tail.reverse xs.reverse.tail equals xs.init.reverse xs.reverse.head equals xs.last xs.reverse.last equals xs.head
通过连接在尾部添加操作:::
来实现反转:
def rev[T](xs: List[T]): List[T] = xs match { case List() => xs case x :: xs1 => rev(xs1) ::: List(x) }
当然这样的效率低得很,因为列表要一个一个遍历才能找到最后一个元素。
前缀与后缀
take
和drop
取得或舍去列表指定长度个元素,长度超过时不会抛异常而是返回整个列表
或空列表。
scala> abcde take 2 res8: List[Char] = List(a, b) scala> abcde drop 2 res9: List[Char] = List(c, d, e)
splitAt
在指定位置拆分列表。
xs splitAt n // equals (xs take n, xs drop n)
例:
scala> abcde splitAt 2 res10: (List[Char], List[Char]) = (List(a, b),List(c, d, e))
取得指定元素
通过索引取得指定元素:
scala> abcde apply 2 // rare in Scala res11: Char = c scala> abcde(2) // rare in Scala res12: Char = c
includes
方法取得所有的索引列表:
scala> abcde.indices res13: List[Int] = List(0, 1, 2, 3, 4)
zip
把两个列表组成对偶(二元组),如果长度不一样会丢弃长出来的:
scala> abcde.indices zip abcde res14: List[(Int, Char)] = List((0,a), (1,b), (2,c), (3,d), (4,e)) scala> val zipped = abcde zip List(1, 2, 3) zipped: List[(Char, Int)] = List((a,1), (b,2), (c,3))
如果是为了把元素和索引zip在一起,用zipWithIndex
方法更有效:
scala> abcde.zipWithIndex res15: List[(Char, Int)] = List((a,0), (b,1), (c,2), (d,3), (e,4))
toString 和 mkString
toString
简单字符串化列表
scala> abcde.toString res16: String = List(a, b, c, d, e)
mkString
通过三个参数来指定前后包列表的字符和分隔列表元素的字符:
xs mkString (pre, sep, post)
还有两个变体:
xs mkString sep // equals xs mkString ("", sep, "") xs mkString // equals xs mkString ""
例子:
scala> abcde mkString ("[", ",", "]") res17: String = [a,b,c,d,e] scala> abcde mkString "" res18: String = abcde scala> abcde.mkString res19: String = abcde scala> abcde mkString ("List(", ", ", ")") res20: String = List(a, b, c, d, e)
还有一个addSting
变体让结果添加到StringBuilder
中,而不是作为结果返回:
scala> val buf = new StringBuilder buf: StringBuilder = scala> abcde addString (buf, "(", ";", ")") res21: StringBuilder = (a;b;c;d;e)
列表的转换
List
类的toArray
和Array
类的toList
,列表和数组转来转去。
scala> val arr = abcde.toArray arr: Array[Char] = Array(a, b, c, d, e) scala> arr.toString res22: String = Array(a, b, c, d, e) scala> arr.toList res23: List[Char] = List(a, b, c, d, e)
copyToArray
把列表复制到数组中一会连续的空间内:
xs copyToArray (arr, start)
start
为开始的位置。当然还要保证数组中有足够的空间。例子:
scala> val arr2 = new Array[Int](10) arr2: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0) scala> List(1, 2, 3) copyToArray (arr2, 3) scala> arr2.toString res25: String = Array(0, 0, 0, 1, 2, 3, 0, 0, 0, 0)
elements
提供了通过枚举器访问列表元素的方法:
scala> val it = abcde.elements it: Iterator[Char] = non-empty iterator scala> it.next res26: Char = a scala> it.next res27: Char = b
例:归并排序
归并排序:如果列表长度为0或是1,就算是已经排序好的,直接返回。长度大于1的列表 可以拆成两个长度接近的,每个再递归调用完成排序,再把返回的两个排序好的列表合并。
函数的实现用到了柯里化,接收元素之间的比较大小的函数和要排序的列表:
def msort[T](less: (T, T) => Boolean) (xs: List[T]): List[T] = { def merge(xs: List[T], ys: List[T]): List[T] = (xs, ys) match { case (Nil, _) => ys case (_, Nil) => xs case (x :: xs1, y :: ys1) => if (less(x, y)) x :: merge(xs1, ys) else y :: merge(xs, ys1) } val n = xs.length / 2 if (n == 0) xs else { val (ys, zs) = xs splitAt n merge(msort(less)(ys), msort(less)(zs)) } }
使用的方法:
scala> msort((x: Int, y: Int) => x < y)(List(5, 7, 1, 3)) res28: List[Int] = List(1, 3, 5, 7)
作为一个柯里化的例子,可以用下划线代表末指定的参数列表:
scala> val intSort = msort((x: Int, y: Int) => x < y) _ intSort: (List[Int]) => List[Int] = <function>
如果要改成倒序排序的话,只要换个比较函数:
scala> val reverseIntSort = msort((x: Int, y: Int) => x > y) _ reverseIntSort: (List[Int]) => List[Int] = <function>
上面的intSort
和reverseIntSort
都已经绑定了排序的方法,只要传入待排序的列表:
scala> val mixedInts = List(4, 1, 9, 0, 5, 8, 3, 6, 2, 7) mixedInts: List[Int] = List(4, 1, 9, 0, 5, 8, 3, 6, 2, 7) scala> intSort(mixedInts) res0: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) scala> reverseIntSort(mixedInts) res1: List[Int] = List(9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
List类的高阶函数
这里介绍的方法是List类的方法,所以是在独立的对象上被调用。
Scala中以操作符形式出现的高阶函数更加简洁地处理Java中用循环来处理的问题。
列表间映射
xs map fun
把列表中每个元素用方法处理过后生成新列表。xs
代表List[T]
;fun
代表T => U
的函数。
scala> List(1, 2, 3) map (_ + 1) res29: List[Int] = List(2, 3, 4) scala> val words = List("the", "quick", "brown", "fox") words: List[java.lang.String] = List(the, quick, brown, fox) scala> words map (_.length) res30: List[Int] = List(3, 5, 5, 3) scala> words map (_.toList.reverse.mkString) res31: List[String] = List(eht, kciuq, nworb, xof)
flatMap
和map
类似,但它把所有元素连成一个列表:
scala> words map (_.toList) res32: List[List[Char]] = List(List(t, h, e), List(q, u, i, c, k), List(b, r, o, w, n), List(f, o, x)) scala> words flatMap (_.toList) res33: List[Char] = List(t, h, e, q, u, i, c, k, b, r, o, w, n, f, o, x)
flatMap
和map
合作建立出所有1 <= j < i < 5
的(i, j)
对偶:
scala> List.range(1, 5) flatMap ( i => List.range(1, i) map (j => (i, j)) ) res34: List[(Int, Int)] = List((2,1), (3,1), (3,2), (4,1), (4,2), (4,3))
上面的代码List.range(1, 5)
产生从1到5的整数列表。对于其中的每项i
再产生1
到
i
的列表。map
产生(i, j)
元组列表,这里的j<i
。flatMpa
对每个1到5之间的i
产生列表,并连接所有列表得到结果。
等同于以下循环结构:
for (i <- List.range(1, 5); j <- List.range(1, i)) yield (i, j)
foreach
没有返回结果(或返回Unit
)。如下对sum
变量累加,但是没有返回值:
scala> var sum = 0 sum: Int = 0 scala> List(1, 2, 3, 4, 5) foreach (sum += _) scala> sum res36: Int = 15
过滤
xs filter p
,xs
代表List[T]
,p
代表T => Boolean
形式的函数。返回符合的结果列表:
scala> List(1, 2, 3, 4, 5) filter (_ % 2 == 0) res37: List[Int] = List(2, 4) scala> words filter (_.length == 3) res38: List[java.lang.String] = List(the, fox)
partition
方法返回的是所有符合的元素和所有不符合的元素两个列表对。
xs partition p // equals ( xs filter p , xs filter (!p(_)) )
例:
scala> List(1, 2, 3, 4, 5) partition (_ % 2 == 0) res39: (List[Int], List[Int]) = (List(2, 4),List(1, 3, 5))
find
方法只返回第一个符合的元素,一个都不符合返回None
:
scala> List(1, 2, 3, 4, 5) find (_ % 2 == 0) res40: Option[Int] = Some(2) scala> List(1, 2, 3, 4, 5) find (_ <= 0) res41: Option[Int] = None
takeWhile
不断累积符合的结果直到遇到不符合的;dropWhile
不断丢弃不符的元素直到
遇到符合的。
scala> List(1, 2, 3, -4, 5) takeWhile (_ > 0) res42: List[Int] = List(1, 2, 3) scala> words dropWhile (_ startsWith "t") res43: List[java.lang.String] = List(quick, brown, fox)
span
方法组合了takeWhile
和dropWhile
返回一对列表,就像是splitAt
组合了
take
和drop
一样。
xs span p // equals (xs takeWhile p , xs dropWhile p)
和split
一样,span
避免对列表的二次访问:
scala> List(1, 2, 3, -4, 5) span (_ > 0) res44: (List[Int], List[Int]) = (List(1, 2, 3),List(-4, 5))
列表论断
xs forall p
全部符合,xs exits p
存在符合的元素。
scala> def hasZeroRow(m: List[List[Int]]) = | m exists (row => row forall (_ == 0)) hasZeroRow: (List[List[Int]])Boolean scala> hasZeroRow(diag3) res45: Boolean = false
reduce与folder
化简(reduce)
reduceLeft
操作:
List(1, 2, 3, 4).reduceLeft(_ + _) // 相当于 ((1 + 2) + 3) + 4
reduceRight
操作:
List(1, 2, 3, 4).reduceRight(_ + _) // 相当于 ((4 + 3) + 2) + 1
折叠(folder)
左折叠操作符/:
,格式为:(z /: xs) (op)
。其中z
为开始值,xs
为列表,op
为二元操作。
(z /: List(a, b, c)) (op) // equals op(op(op(z,a), b), c)
用树表示:
op / \ op c / \ op b / \ z a
举例:
scala> def sum(xs: List[Int]): Int = (0 /: xs) (_ + _) sum: (List[Int])Int scala> sum(List(1, 2, 3)) // equals 0 + 1 + 2 + 3 res1: Int = 6 scala> def product(xs: List[Int]): Int = (1 /: xs) (_ * _) product: (List[Int])Int scala> product(List(1, 2, 3)) // equals 1 * 1 * 2 * 3 res2: Int = 6
用空格连接所有单词:
scala> ("" /: words) (_ +" "+ _) res46: java.lang.String = the quick brown fox
头上多了一个空格,这样去掉它:
scala> (words.head /: words.tail) (_ +" "+ _) res47: java.lang.String = the quick brown fox
相对的还有右倾斜操作树:\
:
(List(a, b, c) :\ z) (op) // equals op(a, op(b, op(c, z)))
对于组合操作来说,左右折叠是等价的,但效率上有差异。下面两个把元素连接在一起的 方法:
def flattenLeft[T](xss: List[List[T]]) = (List[T]() /: xss) (_ ::: _) def flattenRight[T](xss: List[List[T]]) = (xss :\ List[T]()) (_ ::: _)
采用右折叠的flattenLeft
需要复制第一个元素列表xss.head
一共xss.length-1
次,
所以效率差一些。
注意这里两个版本的实现都要对作为折叠开始值的空列表做类型标注。这是由Scala类型 推断的局限性无法推断出正确的类型。不标注的话会有以下错误:
scala> def flattenRight[T](xss: List[List[T]]) = | (xss :\ List()) (_ ::: _) <console>:15: error: type mismatch; found : List[T] required: List[Nothing] (xss :\ List()) (_ ::: _) ^
在以后的「实现列表」章节中讨论类型推断失败的原因。
如果觉得/:
和:\
看起来不清楚,可以用List提供的foldLeft
和foldRight
方法代替
。比如下面这个查找最大元素的方法:
scala> def findMax(l:List[Int]) = { | l.foldLeft(Integer.MIN_VALUE) {Math.max} | } findMax: (l: List[Int])Int scala> findMax(List(-77,99,-3,18,7,21)) res0: Int = 99
应用场景
reduce与folder更加适合:「两两操作」的结果「继承」到「下一个元素」。
例子:统计字母出现次数
(Map[Char, Int]() /: "Mississippi") { (m, c) => m + (c -> (m.getOrElse(c, 0) + 1)) } res1: scala.collection.immutable.Map[Char,Int] = Map(M -> 1, i -> 4, s -> 4, p -> 2)
例子:使用折叠实现列表反转
def reverseLeft[T](xs: List[T]) = (startvalue /: xs) (operation)
为了写出正确的startvalue
和operation
,从可以出现的最小的列表List()
开始推导:
List() // equals reverseLeft(List()) // equals (startvalue /: List()) (operation) // equals startvalue
所以startvalue
一定是List()
。再代入推导operation
:
List(x) // equals reverseLeft(List(x)) // equals (startvalue /: List(x)) (operation) // equals operation(List(), x) // equals x :: List()
所以具体实现为:
def reverseLeft[T](xs: List[T]) = (List[T]() /: xs) {(ys, y) => y :: ys}
扫描(scan)
scanLeft
和scanRight
是把folder
和map
操作结合在一起。返回的结果是包含所有
中间结果的集合:
scala> (1 to 10).scanLeft(0)(_ + _) res2: scala.collection.immutable.IndexedSeq[Int] = Vector(0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55)
排序
xs sort before
,xs
是列表,before
是比较元素x
是否在y
前面的方法。
scala> List(1, -3, 4, 2, 6) sort (_ < _) res48: List[Int] = List(-3, 1, 2, 4, 6) scala> words sort (_.length > _.length) res49: List[java.lang.String] = List(quick, brown, fox, the)
注意前面还提到过一个msort
方法,那个是定义在列表外的。sort
是List类的方法。
sortWith
排序:
scala> "Mary has a little lamb".split(" ").sortWith(_.length < _.length) res0: Array[String] = Array(a, has, Mary, lamb, little)
List伴生对象的方法
下面介绍的方法是伴生对象scala.List
的,创建列表的工厂方法和特定类型列表的操作。
通过元素创建列表
apply
方法:
List(1, 2, 3) // is actually List.apply(1, 2, 3)
按数值范围创建列表
range
参数可以是:开始、结束、步长:
scala> List.range(1, 5) res51: List[Int] = List(1, 2, 3, 4) scala> List.range(1, 9, 2) res52: List[Int] = List(1, 3, 5, 7) scala> List.range(9, 1, -3) res53: List[Int] = List(9, 6, 3)
创建重复元素的列表
make
方法:
scala> List.make(5, 'a') res54: List[Char] = List(a, a, a, a, a) scala> List.make(3, "hello") res55: List[java.lang.String] = List(hello, hello, hello)
解除Zip列表
unzip
把二元组列表分成两个列表:
scala> val zipped = "abcde".toList zip List(1, 2, 3) zipped: List[(Char, Int)] = List((a,1), (b,2), (c,3)) scala> List.unzip(zipped) res56: (List[Char], List[Int]) = (List(a, b, c), List(1, 2, 3))
Scala类型系统要求类方法能处理所有类型,而unzip只处理二元组列表。所以unzip不能像 zip方法一样放在类里而只能放在伴生对象里。
连接列表
flatten
方法只能处理包含子列表的列表所以不能放在List类里。只能放在伴生对象中。
scala> val xss = | List(List('a', 'b'), List('c'), List('d', 'e')) xss: List[List[Char]] = List(List(a, b), List(c), List(d, e)) scala> List.flatten(xss) res57: List[Char] = List(a, b, c, d, e)
concat
方法把多个列表作为可变长参数形式接收:
scala> List.concat(List('a', 'b'), List('c')) res58: List[Char] = List(a, b, c) scala> List.concat(List(), List('b'), List('c')) res59: List[Char] = List(b, c) scala> List.concat() res60: List[Nothing] = List()
映射与测试配对
map2
方法接收两个列表,分别作为方法的两个参数:
scala> List.map2(List(10, 20), List(3, 4, 5)) (_ * _) res61: List[Int] = List(30, 80)
exist2
也是接收两个列表,分别作为方法的两个参数:
scala> List.forall2(List("abc", "de"), | List(3, 2)) (_.length == _) res62: Boolean = true scala> List.exists2(List("abc", "de"), | List(3, 2)) (_.length != _) res63: Boolean = false
了解Scala的类型推断方法
Scala的类型推导器是基于流的。
下面是用占位符_
推导出的参数类型:
scala> abcde sort (_ > _) res65: List[Char] = List(e, d, c, b, a)
例如对于List[Char]
类型的列表abcd
,abcd
的成员都是Char
。所以
abcd.sort(_ > _)
的两个参数也只会是Char
。所以类型被推导为:
(_ > _) // trans to ((x: Char, y: Char) => x > y)
再来看msort
方法:
scala> msort((x: Char, y: Char) => x > y)(abcde) res64: List[Char] = List(e, d, c, b, a)
msort
方法却不能用占位符:
scala> msort(_ > _)(abcde) <console>:12: error: missing parameter type for expanded function ((x$1, x$2) => x$1.$greater(x$2)) msort(_ > _)(abcde) ^
原因是对于func(args)
这样的方法,先看func
是否有已经的类型。如果有的话这个类型
就被用来做参数预期类型的推断。而msort(_ > _)(abcde)
这个类型是柯里化的、多态的
方法类型,参数类型是(T, T) => Boolean
,返回类型是从List[T]
到List[T]
的函数
。无法推断第一个参数的类型。所以类型推断器要参数的类型信息。
想要用占位符的话,只能把参数类型传给msort
,改为msort[Char]
:
scala> msort[Char](_ > _)(abcde) res66: List[Char] = List(e, d, c, b, a)
还有一个方法是交换参数顺序,这样可以用第一个列表的类型来推断比较方法的类型了:
// same implementation as msort, // but with arguments swapped def msortSwapped[T](xs: List[T])(less: (T, T) => Boolean): List[T] = { } scala> msortSwapped(abcde)(_ > _) res67: List[Char] = List(e, d, c, b, a)
需要推断多态方法类型时只会参考第一个参数列表,所以在柯里化方法有两个参数列表时 第二个参数不会用来决定方法类型参数。所以这种方案隐含以下的库方法设计原则:
如果参数包括若干个非函数参数与一个函数参数的组合时,要把函数参数独自放在柯里化 参数列表的最后面。这样方法的正确实例类型就可以通过非函数参数推断出来,推断出来的 类型还可以转面用来完成函数参数的类型检查。调用函数的时候也可以写出更加简洁的 字面量。
再来看更加复杂的折叠操作:
(xss :\ List[T]()) (_ ::: _)
上面的表达式提供了明确的类型参数的原因是这个右折叠操作的类型取决于两个变量:
(xs :\ z) (op)
这里把列表xs
的类型记为A
,如:xs: List[A]
;而开始值z
有可能是类型B
。对应
的操作op
必须以A
和B
的值为参数并返回类型B
的结果,即:op: (A, B) => B
。
从上面的描述可以看出:这里的op
方法要知道A
与B
两个类型。A
一定与List
有关
,但是B
不一定与List
有关,所以推不出来。所以下面的表达式是编译不过的:
(xss :\ List()) (_ ::: _) // this won't compile
上面表达式中z
的类型为List[Nothing]
,据此推断器把B
的类型定为Nothing
:
(List[T], List[Nothing]) => List[Nothing]
这就意味着输出与输出都是空列表。
就是因为这个问题,所以在柯里化的方法中,方法类型只取决于第一段参数。但是如果不
这么做的话,推断器还是没有办法取得op
的类型。所以只能程序员明确指定类型。
所以Scala采用的局部的、基于流的类型推断方法还是比较有局限性的;不如ML或是Haskell 采用的更加全局化的Hindley-Milner类型推断方式。但是对于面向对象的分支类型处理比 Hindley-Mlner更加优雅。由于这些局限性在比较极端的情况下才遇到,所以在极端情况下 还是明确标类型吧。
另外在遇到多态类型错误时,添加上你认为应该是正确的类型标注也是一种排错方式。