快学Scala第13章----集合
本章要点
主要的集合特质Scala集合继承层级中的关键特质: Iterable值的是那些能申城用来访问集合中所有元素的Iterator的集合,类似于C++的迭代器。 val coll = ... //某种Iterable
val iter = coll.iterator
while (iter.hasNext) {
对iter.next() 履行某种操作
} Seq是1个有前后次序的值的序列,例如数字或列表。IndexedSeq允许我们通过使用下标的方式快速访问元素。 可变和不可变集合Scala同时支持可变和不可变的集合。Scala优先采取不可变集合,因此你可以安全的同享其援用。任何对不可变集合的修改操作都返回的是1个新的不可变集合,它们同享大部份元素。 def digits(n: Int): Set[Int] = {
if (n < 0) digits(-n)
else if (n < 10) Set(n)
else digits(n / 10) + (n % 10)
} 这个例子利用递归不断的产生新的集合,但是要注意递归的深度。 序列最重要的不可变序列: Vector是ArrayBuffer的不可变版本,和C++的Vector1样,可以通过下标快速的随机访问。而Scala的Vector是以树形结构的情势实现的,每一个节点可以有不超过32个子节点。这样对有100万的元素的向量而言,只需要4层节点。 Range表示1个整数序列,例如0,1,2,3,4,5 或 10,20,30 . Rang对象其实不存储所有值而只是起始值、结束值和增值。 最有用可变序列: 列表在Scala中,列表要末是Nil(空列表),要末是1个head元素加上1个tail,而tail又是1个列表。例如: val digits = List(4,2)
digits.head // 4
digits.tail // List(2)
digits.tail.head // 2
digits.tail.tail // Nil :: 操作符从给定的头和尾创建列表: 9 :: List(4,2) // List(9,2)
// 同等于
9 :: 4 :: 2 :: Nil // 这里是又结合的 遍历链表:可使用迭代器、递归或模式匹配 def sum(lst: List[Int]): Int = {
if (lst == Nil) 0 else lst.head + sum(lst.tail)
}
def sum(lst: List[Int]): Int = lst match {
case Nil => 0
case h :: t => h + sum(t)
} 可变列表可变的LinkedList既可以修改头部(对elem援用赋值),也能够修改尾部(对next援用赋值): val lst = scala.collection.mutable.LinkedList(1,-2,7,-9)
// 修改值
var cur = lst
while (cur != Nil) {
if (cur.elem < 0) cur.elem = 0
cur = cur.next
}
// 去除每两个中的1个
var cur = lst
while (cur != Nil && cur.next != Nil) {
cur.next = cur.next.next
cur = cur.next
} 注意: 如果你想要将列表中的某个节点变成列表的最后1个节点,你不能够将next援用设为Nil,而应当将next援用设为LinkedList.empty。也不要设为null,不然在遍历该链表时会遇到空指针毛病。 集集是不重复的元素的集合,与C++中的set相同。集其实不保存元素插入的顺序,默许情况下,集以哈希集实现。 val weekdays = scala.collection.mutable.LinkedHashSet("Mo","Tu","We","Th","Fr") 对SortedSet已排序的集使用红黑树实现的。Scala没有可变的已排序的集,前面已讲过。 val digits = Set(1,2,9)
digits contains 0 // false
Set(1,2) subsetOf digits // true
val primes = Set(2,3,5,7)
digits union primes // Set(1,5,7,9)
// 同等于
digits | primes // 或 digits ++ primes
digits intersect primes // Set(2, 7)
// 同等于
digits & primes
digits diff primes // Set(1,9)
// 同等于
digits -- primes 用于添加或去除元素的操作符1般而言, + 用于将元素添加到无前后次序的集合,而+: 和 :+ 则是将元素添加到有前后次序的集合的开头或是结尾。 Vector(1,3) :+ 5 // Vector(1,5)
1 +: Vector(1,3) // Vector(1,3) 经常使用方法Iterable特质最重要的方法: Seq特质在Iterable特质的基础上又增加的1些方法: 将函数映照到集合map方法可以将某个函数利用到集合的每个元素并产出其结果的集合。例如: val names = List("Peter","Paul","Mary")
names.map(_.toUpperCase) // List("PETER","PAUL","MARY")
// 同等于
for (n <- names) yield n.toUpperCase 如果函数产出1个集合而不是单个值得话,则使用flatMap将所有的值串接在1起。例如: def ulcase(s: String) = Vector(s.toUpperCase(),s.toLowerCase())
names.map(ulcase) // List(Vector("PETER","peter"),Vector("PAUL","paul"),Vector("MARY","mary"))
names.flatmap(ulcase) // List("PETER","peter","paul","MARY","mary") collect方法用于偏函数—并没有对所有可能的输入值进行定义的函数。例如: "⑶+4".collect {case '+' => 1; case '-' => -1} // Vector(⑴,1)
"⑶+4".collect {case '-' => -1} // Vector(⑴)
"⑶+4".collect {case '+' => 1} // Vector(1)
"⑶+4".collect {case '*' => 1} // Vector() foreach方法将函数利用到各个元素但不关心函数的返回值。 names.foreach(println) 化简、折叠和扫描reduceLeft、reduceRight、foldLeft、foldRight、scanLeft、scanRight方法将会用2元函数来组合集合中的元素: List(1,9).reduceLeft(_ - _) // ((1 - 7) - 2) - 9
List(1,9).reduceRight(_ - _) // 1 - (7 - (2 - 9))
List(1,9).foldLeft(0)(_ - _) // 0 - 1 ⑺ - 2 - 9
List(1,9).foldRight(0)(_ - _) // 1 - (7 - (2 - (9 - 0)))
(1 to 10).scanLeft(0)(_ + _) // Vector(0,6,10,15,21,28,36,45,55) 拉链操作前面的章节已讲过拉链操作。除zip方法外,还有zipAll和zipWithIndex方法 List(5.0,20,0,9.7,3.1,4.3).zipAll(List(10,2),0.0,1) // List((5.0,10),(20.0,2),(9.7,1),(3.1,(4.3,1))
"Scala".zipWithIndex // Vector(('S',0),('c',('a',('l',3),4) ) 迭代器迭代器的好处就是你不用将开消很大的集合全部读进内存。例如读取文件操作,Source.fromFile产出1个迭代器,使用hasNext和next方法来遍历: while (iter.hasNext)
对 iter.next() 履行某种操作 这里要注意迭代器多指向的位置。在调用了map、filter、count、sum、length等方法后,迭代器将位于集合的尾端,你不能再继续使用它。而对其他方法而言,比如find或take,迭代器位于已找到元素或已获得元素以后。 流流是1个尾部被懒计算的不可变列表—–也就是说,只有当你需要时它才会被计算。 def numsFrom(n: BigInt): Stream[BigInt] = n #:: numsFrom(n + 1)
val tenOrMore = numsFrom(10) // 得到1个 Stream(10,?) 流对象,尾部未被求值
temOrMore.tail.tail.tail // Stream(13,?) 流的方法是懒履行的。例如: val squares = numsFrom(1).map(x => x * x) // 产出 Stream(1,?)
// 需要调用squares.tail来强迫对下1个元素求值
squares.tail // Stream(4,?)
// 使用take和force强迫求指定数量的值
squares.take(5).force // Stream(1,9,16,25) 注意: 不要直接使用 squares.force, 这样将会是1个无穷的流的所有成员求值, 引发OutOfMemoryError 。 懒视图利用view方法也能够实现懒履行,该方法产出1个其方法总是被懒履行的集合。例如: val powers = (0 until 1000).view.map(pow(10,_))
powers(100) //pow(10,100)被计算,其他值的幂没有被计算 和流不同,view连第1个元素都不求值,除非你主动计算。view不缓存任何值,每次调用都要重新计算。 (0 to 1000).map(pow(10,_)).map(1 / _) // 1
(0 to 1000).view.map(pow(10,_)).map(1 / _).force // 2 第1个式子 会产出两个集合,第1个集合的每个元素是pow(10,n),第2个集合是第1个集合中每一个集合中的元素取倒数。 而第2个表达式使用了视图view,当动作被强迫履行时,对每一个元素,这两个操作是同时履行的,不需要额外构建中间集合。 与Java集合的互操作线程安全的集合Scala类库提供了6个特质,你可以将它们混入集合,让集合的操作变成同步: 例如: val scores = new scala.collection.mutable.HashMap[String,Int] with scala.collection.mutable.SynchronizedMap[String,Int] 固然,还有更高效的集合,例如ConcurrentHashMap或ConcurrentSkipListMap,比简单的用同步方式履行所有的方法更加有效。 并行集合集合的par方法产出当前集合的1个并行实现,例如sum求和,多个线程可以并发的计算不同区块的和,在最后这部份结果被汇总到1起。 coll.par.sum 你可以通过对要遍历的集合利用par并行for循环 for(i <- (0 until 100).par) print(i + " ") 而在 for/yield循环中,结果是顺次组装的: for(i <- (0 until 100).par) yield i + " " 这里要注意变量是同享变量,还是循环内的局部变量: var count = 0
for (c <- coll.par) {if (c % 2 == 0) count += 1} // error **注意: **par方法返回的并行集合的类型为扩大自ParSeq、ParSet或ParMap特质的类型,所有这些特质都是ParIterable的子类型。这些其实不是Iterable的子类型,因此你不能将并行集合传递给预期Iterable、Seq、Set、Map方法。你可以用ser方法将并行集合转换回串行集合,也能够实现接受通用的GenIterable、GenSeq、GenSeq、GenMap类型的参数的方法。 说明: 其实不是所有的方法都可以被并行化。例如reduceLeft、reduceRight要求每一个操作符依照顺序前后被利用。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |