Scala 集合框架:List
本文的示例代码来自 <Beginning Scala> ,由于Scala中的集合框架已经重写, 这里的代码输出都是在 Scala 2.8.0RC1 下运行得出的,跟原书中有一定的差距。
1. Range
collections 可以拥有任意数量的元素,或者局限于零个或一个元素(如 Option ),Collections 可以是strict 或是 lazy 的。Lazy Collection 可以拥有不消耗内存的元素,直到它们被访问。例如一个 Range 类:
scala> 0 until 10 by 3 res7: scala.collection.immutable.Range = Range(0, 3, 6, 9)
在这里,Range 中的元素在被访问之前并不会被实例化,下面代码只访问了前5个元素:
scala> (1 to Integer.MAX_VALUE - 1).take(5) res8: scala.collection.immutable.Range = Range(1, 2, 3, 4, 5)
这个例子中不会消耗很多内存,因为只有需要的元素才被创建。
2. 构造一个列表
Collection 可能是可变的(即引用的内容可以被改变),也可能是不可变的(即一个引用所指向的东西不可以被改变),但注意的是不可变的Collections 可以包含可变的项目。
Scala 的list[T]是一个linked list 类型的T,代表它可以是任意类型的顺序列表。在内部,List 是由 :: 构造的, 即Scala 的 :: 类,例如:
scala> 1::2::3::Nil res9: List[Int] = List(1, 2, 3)
实际上,它和下面的代码是相同的:
scala> new ::(1, new ::(2, new ::(3, Nil))) res10: scala.collection.immutable.::[Int] = List(1, 2, 3)
3. 添加元素
:: 接收 head 为单个元素,而tail 则是另外一个 list , 其中 :: 左边的是 head,而 :: 右边则是tail。
也可以利用 apply 方法生成一个list :
def apply[T](param: T*)
添加一个元素:
scala> val x = List(1,2,3,4) x: List[Int] = List(1, 2, 3, 4) scala> 99::x res12: List[Int] = List(99, 1, 2, 3, 4)
注意在这里 x 并没有改变,而是创建了一个有新的head 和老的tail 的list。这个过程是很快的,是一个复杂度为O(1)的操作。
4. 归并列表
可以归并两个列表从而生成一个新的List,这个操作也是O(n),其中 n 是第一个List中的元素的个数。
scala> val x = List(1,2,3) scala> val y = List(99, 98, 97) scala> x ::: y res3: List[Int] = List(1, 2, 3, 99, 98, 97)
过滤filter:
scala> List(1,2,3).filter(x => x % 2 == 1)
5. 过滤
删除元素:filterNot
scala> List(1,2,3).filterNot(x => x % 2 == 1) res25: List[Int] = List(2)
也可以编写一个isOdd方法作为参数,Scala 会自动提升这个方法为函数:
scala> def isOdd(x: Int) = x % 2 == 1 isOdd: (Int)Boolean scala> List(1,2,3,4,5).filter(isOdd) res6: List[Int] = List(1, 3, 5)
filter 可以作用于包含任意类型的collections:
scala> "99 Red Balloons".toList.filter(Character.isDigit) res26: List[Char] = List(9, 9)
注意在这里 Scala 将 static 的isDigit 方法提升为一个函数,这也同时表明了和 Java的交操作性。
另一种取得元素的方法是 takeWhile,它会返回所有的元素,直到遇到一个元素能使函数返回false。例如:
scala> "Elwood eats mice".takeWhile(c => c != ' ') res27: String = Elwood
6. 应用函数
List 和 Seq 中的map 方法能基于一个函数来转换一个Collection中的每个元素。
并且返回的collection 的个数和原始collection 中的个数是相同的,但可以是不同的类型:
scala> List("A", "Cat").map(_.length)
res31: List[Int] = List(1, 3)
也可以从类中获取属性:
scala> trait Person {def first: String }
defined trait Person
scala> val d = new Person {def first = "David" }
scala> val e = new Person {def first = "Elwood"}
scala> val a = new Person {def first = "Archer"}
scala> List(a, d, e).map(_.first)
res35: List[String] = List(Archer, David, Elwood)
7. 排序
List 也有一个sortWith 方法,它接收一个比较两个参数的函数,例如:
scala> List("b", "a", "elwood", "archer").sort(_ < _)
res48: List[java.lang.String] = List(a, archer, b, elwood)
默认也提供了排序方法(注 Scala 2.8 中sort方法已经 @Deprecated)
res31: List[Int] = List(1, 2, 45, 99) scala> List(99, 2, 1, 45).sorted res32: List[Int] = List(1, 2, 45, 99) scala> List(99, 2, 1, 45).sorted.reverse res33: List[Int] = List(99, 45, 2, 1)
支持对普通类进行排序:
trait Person {
def age: Int
def first: String
def valid: Boolean
}
def validByAge(in: List[Person]) =
in.filter(_.valid).
sort(_.age < _.age). map(_.first)
8. reduceLeft 和 foldLeft reduceLeft
对集合中相邻的元素进行操作,并且上一次的运行結果将会进行下一次计算。例如,寻找最大值:
scala> List(8, 6, 22, 2).reduceLeft(_ max _) res50: Int = 22
寻找最大长度的字符串:
scala> List("moose", "cow", "A", "Cat").
reduceLeft((a, b) => if (a.length > b.length) a else b)
res41: java.lang.String = moose
寻找最短长度的字符串:
scala> List("moose", "cow", "A", "Cat").
reduceLeft((a, b) => if (a.length < b.length) a else b) res42: java.lang.String = A
注意 reduceLeft 会在操作Nil List 时抛出一个异常。 FoldLeft 和 reduceLeft 是类似的,但它以一个 seed 值作为初值,且返回类型必须和seed 的类型相同。
scala> List(1,2,3,4).foldLeft(0) (_ + _)
res43: Int = 10
scala> List(1,2,3,4).foldLeft(1) (_ * _)
res44: Int = 24
scala> List("b", "a", "elwood", "archer").foldLeft(0)(_ + _.length)
res51: Int = 14
scala> val n = (1 to 3).toList
n: List[Int] = List(1, 2, 3)
9. 列表解析
scala> val n = (1 to 3).toList n: List[Int] = List(1, 2, 3) scala> n.map(i => n.map(j => i * j)) res53: List[List[Int]] = List(List(1, 2, 3), List(2, 4, 6), List(3, 6, 9))
如果最终只想得到一个列表:
scala> n.flatMap(i => n.map(j => i * j)) res58: List[Int] = List(1, 2, 3, 2, 4, 6, 3, 6, 9)
从語法来讲,嵌套使用 map, flatMap以及filter 看起来代码不易读,如果想得到1到10之内的各个对应的奇偶数的积,可以用下面的写法:
scala> def isOdd(in: Int) = in % 2 == 1 scala> def isEven(in: Int) = !isOdd(in) scala> val n = (1 to 10).toList scala> n.filter(isEven).flatMap(i => n.filter(isOdd).map(j => i * j)) res60: List[Int] = List(2, 6, 10, 14, 18, ... 10, 30, 50, 70, 90)
用 for 改写之后显得更加清晰:
scala> for {i <- n if isEven(i); j <- n if isOdd(j)} yield i * j
res59: List[Int] = List(2, 6, 10, 14, 18, ... 10, 30, 50, 70, 90)
注意这里的 for 语句并不是一个循环的结构,编译器最终仍将它解析成为map,flatMap和filter,事实上,下面的两句代码所生成的字节码是相同的:
n.filter(isEven).flatMap(i => n.filter(isOdd).map(j => i * j))
for {i <- n if isEven(i); j <- n if isOdd(j)} yield i * j
for 可以用于任意的类,包括用户定义的实现了map,flatMap,filter和foreach的类,所以你可以创建自己能在for中使用的类。 模式匹配的語法和List 的构造語法是相同的,例如匹配 List[Int], case 1::Nil 会匹配List(1),而case 1::2::Nil会匹配 List(1,2),case 1::rest 则会匹配所有第一个元素为1的list,下面的代码将罗马数字改写成阿拉伯数字:
def roman(in: List[Char]): Int = in match {
case 'I' :: 'V' :: rest => 4 + roman(rest)
case 'I' :: 'X' :: rest => 9 + roman(rest)
case 'I' :: rest => 1 + roman(rest)
case 'V' :: rest => 5 + roman(rest)
case 'X' :: 'L' :: rest => 40 + roman(rest)
case 'X' :: 'C' :: rest => 90 + roman(rest)
case 'X' :: rest => 10 + roman(rest)
case 'L' :: rest => 50 + roman(rest)
case 'C' :: 'D' :: rest => 400 + roman(rest)
case 'C' :: 'M' :: rest => 900 + roman(rest)
case 'C' :: rest => 100 + roman(rest)
case 'D' :: rest => 500 + roman(rest)
case 'M' :: rest => 1000 + roman(rest)
case _ => 0
}
在 Scala 中,List是最经常被使用的集合类型,所以需要熟练地掌握。
Related posts:
评论