+
将元素添加到无先后次序的集合中,+:
和:+
向前或向后追加到序列,++
将两个集合串接在一起,-
和--
移除元素。 <<trait>>
Iterable
<<trait>> <<trait>> <<trait>>
Seq Set Map
<<trait>> <<trait>> <<trait>>
IndexedSeq SortedSet SortedMap
Iterable特质指的是那些能生成用来访问集合中所有元素的Iterable的集合:
//遍历集合的最基本的方式
val coll = ... //某种Iterable
val iter = coll.iterator
while(iter.hasNext)
对iter.next()执行某种操作
Seq是一个有先后次序的值的序列,比如数组或列表。IndexedSeq允许我们通过整型下标快速的访问任意元素。如:ArrayBuffer是带下标的,但链表不是。
Set是一组没有先后次序的值。在SortedSet中,元素以某种排过序的顺序被访问。
Map是一组(键,值)对偶。SortedMap按照键的排序访问其中的实体。
这个继承层级和Java很相似,同时有一些不错得到改进:
每个Scala集合特质或类都有一个带有apply方法的伴生对象,这个apply方法可以用来构建该集合中的实例。如:
Iterable(0xFF, 0xFF00, 0xFF0000)
Set(Color.RED, Color.GREEN, Color.BLUE)
Map(Color.RED->0xFF0000,
Color.GREEN->0xFF00,
Color.BLUE->0xFF)
SortedSet("Hello","World")
这样的设计叫做”统一创建原则”。
Scala优先采用不可变集合,scala.collection包中的伴生对象产出不可变的集合。如:
scala.collection.Map("Hello"->42)
//是一个不可变的映射。
不止如此,总被引入的scala包和Predef对象里有指向不可变特质的类型别名List,Set和Map。
举例来说,Predef.Map
和scala.collection.immutable.Map
是一回事。
使用import scala.collection.mutable
,就可以用Map得到不可变的映射,用mutable.Map
得到可变的映射。
计算某个整数中所有出现过的阿拉伯数字的集:
def digits(n:Int):Set[Int] =
if(n < 0) digits(-n)
else if (n < 10) Set(n)
else digits(n / 10) + (n % 10)
这个方法从包含单个数字的集开始,每一步,添加进另外一个数字,不过,添加某个数字并不会改变原有的集,而是构造出一个新的集。
# 最重要的不可变序列
<<trait>>
Seq <<trait>> List Stream Stack Queue
IndexedSeq | _____________ | |
Vector Range
Vector是ArrayBuffer的不可变版本:一个带下标的序列,支持快速的随机访问,Vector是以树形结构的形式实现的,每个节点可以有不超过32个子节点。对于一个有100万元素的向量而言,我们只需要四层节点(10^3==2^10,10^6==32^4),访问一个Vector中的某个元素只需要4跳,而在List中,同样的操作平均需要500000跳。(从第一个开始,一个接一个的找下一个。)
Range表示一个整数序列,比如0,1,2,3,4,5,6,7,或10,20,30.当然,Range对象并不存储所有值,而只是起始值,结束值和增值。可以用to和until方法来构造Range对象。
# 最有用的可变序列
<<trait>>
Seq <<trait>> Stack Queue Priority Queue LinkedList Double LinkedList
IndexedSeq ||
ArrayBuffer
在Scala中,列表要么是Nil(空列表),要么是一个head元素加上一个tail,而tail又是一个列表。
val digits = List(4,2)
digits.head == 4
digits.tail == List(2)
::操作符从给定的头和尾创建一个新列表,如
9 :: List(4,2)
就是List(9,4,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) // h是lst.head 而t是lst.tail
}
#用scala的库函数
List(9,4,2).sum
可变的LinkedList和不可变的List相似,你可以通过对elem引用赋值来修改其头部,对next引用赋值来修改其尾部。
如:
# 把所有负值改为0
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
}
# 并不是给head和tail赋值
# 去除每两个元素中的一个
var cur = lst
while(cur != Nil && cur.next != Nil){
cur.next = cur.next.next
cur = cur.next
}
# cur用起来像是迭代器,但实际上它的类型是LinkedList
除LinkedList外,Scala还提供一个DoubleLinkedList
,它多带一个prev
引用。
如果想要把列表中的某个节点变成列表中的最后一个节点,你不能将next引用设为Nil,而应将它设为LinkedList.empty,也不要将它设为null,不然遍历列表时会遇到空指针错误。
集是不重复元素的集合。尝试将已有元素加入没有效果。
如:
Set(2,0,1) + 1 == Set(2,0,1)
和列表不同,集不保留元素插入的顺序。缺省情况下,集是以哈希集实现的,其元素根据hashCode方法的值进行组织。(Scala和Java一样,每个对象都有hashCode方法。)
举例来说,如果你遍历
Set(1,2,3,4,5,6)
元素被访问到的次序是:
5,1,6,2,3,4
在哈希集中查找元素要比在数组或是列表中快得多。
链式哈希集可以记住元素被插入的顺序。它会维护一个链表来达到这个目的,如:
val weekdays = scala.collection.mutable.LinkedHashSet("Mo","Tu","We","Th","Fr","Sa","Su")
# 如果你想要按照已排序的顺序来访问集中的元素,用SortedSet
scala.collection.immutable.SortedSet(1,3,2,4,5,6)
已排序的集是用红黑树实现的。
位集(bit set)是集的一种实现,以一个字位序列的方式存放非负整数。如果集中有i,则第i个字位是1.这是个很高效的实现,只要最大元素不是特别的大。scala提供了可变和不可变的两个BitSet类。
contains方法检查某个集是否包含给定的值。subsetOf方法检查某个集当中的所有元素是否都被另一个集包含。
val digits = Set(1,7,2,9) digits contains 0 //false Set(1,2) subsetOf digits //true
union, intersect 和diff方法执行通常的集操作。也可以写作| , & , &~,union也可以写作 ++ ,diff也可以写作 --
如:
val digits = Set(1,7,2,9) val primes = Set(2,3,5,7) digits union primes = Set(1,2,3,5,7,9) digits & primes = Set(2,7) digits -- primes = Set(1,9)
操作符 | 描述 | 集合类型 |
---|---|---|
coll:+elem elem:+coll | 有elem被追加到尾部或头部的与coll类型相同的集合 | Seq |
coll+elem coll+(e1,e2,…) | 添加了给定元素的与coll类型相同的集合 | Set,Map |
coll-elem coll-(e1,e2,…) | 给定元素被移除的与coll类型相同的集合 | Set,Map,ArrayBuffer |
coll++coll2 coll2++:coll | 与coll类型相同的集合,包含了两个集合的元素 | Iterable |
coll–coll2 | 移除了coll2中元素的与coll类型相同的集合(用diff来处理) | Set,Map,ArrayBuffer |
elem :: lst lst2:::lst | 被向前追加了元素或给定列表的列表。和+: ++:的作用相同 | List |
list:::list2 | 等同于list++:list2 | List |
set | set2 set & set2 set &~ set2 | 并集,交集,和两个集的差异。|==++,&~ == – | Set |
coll += elem coll+=(e1,e2,…) coll ++=coll2 coll -= elem coll -= (e1,e2,..) coll -= coll2 | 通过添加或移除元素来修改coll | 可变集合 |
elem +=: coll coll2 ++=: coll 通过向前追加给定元素或集合来修改coll | 通过向前追加给定元素或集合来修改coll | ArrayBuffer |
一般,+用于将元素添加到无先后次序的集合,而 +:和 :+ 则是将元素添加到有先后次序的集合的开头或末尾。
Vector(1,2,3):+ 5 //Vector(1,2,3,5)
1 +: Vector(1,2,3) //Vector(1,1,2,3)
+:, :+
这种操作符都返回一个新的集合(和原集合类型保持一致),不会修改原有的集合。而可变集合有 += 操作符用于修改左侧操作元。例如:
val numbers = ArrayBuffer(1,2,3)
numbers += 5 //将5添加到numbers
对于不可变集合,你可以在var上使用+=
或:+=
, 如:
var numbers = Set(1,2,3)
numbers += 5 //将numbers设为不可变的集 numbers + 5
var numberVector = Vector(1,2,3)
numberVector :+= 5 //在这里不能用 +=, 因为Vecotr没有 + 操作符
要移除元素,使用-操作符
Set(1,2,3) - 2 // Set(1,3)
使用++来一次添加多个元素:
coll ++ coll2 将产出一个与coll类型相同,且包含coll和coll2中所有元素的集合。类似的,--操作符将一次移除多个元素
方法 | 描述 |
---|---|
head,last,headOption,lastOption | 返回第一个或最后一个元素;或者,以Option返回 |
tail,init | 返回除第一个或者最后一个元素外其余的部分 |
length,isEmpty | 返回集合长度;或者,当长度为0时返回true |
map(f),foreach(f),flatMap(f),collect(pf) | 将函数应用到所有元素 |
reduceLeft(op),reduceRight(op),foldLeft(init)(op),foldRight(init)(op) | 以给定顺序将二元操作应用到所有元素 |
reduce(op),fold(init)(op),aggregate(init)(op,combineOp) | 以非特定顺序将二元操作应用到所有元素 |
sum,product,max,min | 返回和或乘积(前提是元素类型可以被隐式转换成Numeric特质),或者,最大值或最小值(前提是元素类型可以被转换成Ordered特质) |
count(pred),forall(pred),exists(pred) | 返回满足前提表达式的元素计数,所有元素都满足时返回true,或者至少有一个元素满足时返回true |
filter(pred),filterNot(pred),partition(pred) | 返回所有满足前提表达式的元素,所有不满足的元素,或者这两组元素组成的对偶 |
takeWhile(pred),dropWhile(pred),span(pred) | 返回满足前提表达式的一组元素(直到遇到第一个不满足的元素);所有其他元素;或者,这两组元素组成的对偶 |
take(n),drop(n),splitAt(n) | 返回头n个元素;所有其他元素;或者这两组元素组成的对偶 |
takeRight(n),dropRight(n) | 返回最后n个元素;或者所有其他元素 |
slice(from,to) | 返回位于从from到to结束这个区间内的所有元素 |
zip(coll2),zipAll(coll2,fill,fill2),zipWithIndex | 返回由本集合元素和另一个集合的元素组成的对偶 |
grouped(n),sliding(n) | 返回长度为n的子集合迭代器,grouped产生下标为0 until n的元素,然后是n until 2*n的元素,以此类推;sliding产出下标为0 until n的元素,然后是下标为1 until n+1的元素,以此类推 |
mkString(before,between,after),addString(sb,before,between,after) | 做出一个由所有元素组成的字符串,将给定字符串分别添加到首个元素之前,每个元素之间,以及最后一个元素之后。第二个方法将该字符串追加到字符串构建器(string builder) 当中。 |
toIterable,toSeq,toIndexedSeq,toArray,toList,toStream,toSet,toMap | 将集合转换为指定类型的集合 |
copyToArray(arr),copyToArray(arr,start,length),copyToBuffer(buf) | 将元素拷贝到数组或缓冲当中 |
Seq特质的重要方法:
|方法|描述
|contains(elem),containsSlice(seq),startsWith(seq),endsWith(seq)|返回true,如果该序列:包含给定元素,包含给定序列,以给定序列开始,以给定序列结束
|indexOf(elem),lastIndexOf(elem),indexOfSlice(seq), lastIndexOfSlice(seq) |返回给定元素或序列在当前序列中的首次或末次出现的下标
|indexWhere(pred)|返回满足pred的首个元素的下标
|prefixLength(pred),segmentLength(pred, n)|返回满足pred的最长元素序列的长度,从当前序列的下标0或n开始查找
|padTo(n, fill)|返回当前序列的一个拷贝,将fill的内容向后追加,直到新序列长度达到n
|intersect(seq), diff(seq)|返回多重集合的交集,或序列之间的差异。举例来说,如果a包含5个1而b包含2个1,则a insertsect b
包含2个1,a diff b
包含3个1(他们之间的差异)
|reverse|当前序列的反向
|sorted,sortWith(less), sortBy(f)| 使用元素本身的大小,二元函数less,或者将每个元素映射成一个带先后次序的类型的值的函数f,对当前序列进行排序后的新序列
|permutations,combinations(n) |返回一个遍历所有排列或组合(长度为n的子序列)的迭代器
这些方法从不改变原有集合,它们返回一个与原集合类型相同的新集合,这称为”统一返回原则”。
val names = List("Peter","Paul","Mary")
names.map(_.toUpperCase) // List("PETER","PAUL","MARY")
等同于
for(n <- names) yield n.toUpperCase
# flatMap
def ulcase(s:String)=Vector(s.toUpperCase(),s.toLowerCase())
# ulcase 得到的是一个集合而不是单个值
# names.map(ulcase)
List(Vector("PETER","peter"),Vector("PAUL","paul"),Vector("MARY","mary"))
# names.flatMap(ulcase) 得到
List("PETER","peter","PAUL","paul","MARY","mary")
collect方法用于偏函数(partial function)——那些并没有对所有可能的输入值进行定义的函数。它产出被定义的所有参数的函数值的集合,如:
"-3+4.collect{case '+'=>1;case '-'=> -1} " //Vector(-1,1)
如果应用函数到各个元素仅仅是为了它的副作用二不关心函数值的话,可以用过foreach
names.foreach(println)
coll.foldLeft(init)(op)
.
.
.
op
/ \ op coll(2)
/ \ op coll(1)
/ \ init coll(0)
List(1,7,2,9).foldLeft(0)(_-_) = 0-1-7-2-9 = -19
可以用/:代替foldLeft,本意是让人联想到一棵树的样子
val prices = List(5.0,20.0,9.95)
val quantities = List(10,2,1)
prices zip quantities
//得到 List[(Double,Int)] = List((5.0,10),(20.0,2),(9.95,1))
(prices zip quantities) map(p=>p._1 * p._2)
// List(50.0,40.0,9.95)
物件总价:
(prices zip quantities) map(p=>p._1 * p._2) sum
//如果一个集合比另一个短,
//那么结果中的对偶数量和较短的那个集合的元素数量相同
List(5.0,20.0,9.95) zip List(10,2)
//将得到
List((5.0,10),(20.0,2))
//zipAll方法让你指定较短列表的缺省值:
List(5.0,20.0,9.95).zipAll(List(10,2),0.0,1)//??
//将得到:
List((5.0,10),(20.0,2),(9.95,1))
//zipWithIndex方法返回对偶的列表,其中每个对偶第二个组成部分是每个元素的下标,如:
"scala".zipWithIndex
//得到
Vector(('S',0),('c',1),('a',2),('l',3),('a',4))
//这在计算具备某种属性的元素的下标时很有用,如:
"Scala".zipWithIndex.max
//将得到
('l',3)
//具备最大编码的值的下标为
"Scala".zipWithIndex.max._2
可以用iterator方法从集合获得一个迭代器,对于那些完整构造需要很大开销的集合而言,迭代器很有用。
如,Source.fromFile产出一个迭代器,是因为将整个文件都读取到内存可能并不是很高效的做法。Iterable中有一些方法可以产出迭代器,如grouped
和sliding
.
有了迭代器,就可以用next和hasNext方法来遍历集合中的元素了。
while(iter.hasNext)
对iter.next() 执行某种操作
//也可以用for循环
for(elem <- iter)
对elem执行某种操作
//上述两种循环都会将迭代器移动到集合的末尾,在此之后它就不能再被使用了。
Iterator类定义了一些与集合方法使用起来完全相同的办法。即,除head,headOption,last,lastOption,tail,init,takeRight和dropRight外,都支持。在调用诸如map,filter,count,sum甚至是length方法后,迭代器将位于集合的尾端,不能再继续使用它。
对于其他方法,如find或take,迭代器位于已找到元素或已取得元素之后。
Iterator还可以toArray, toIterable,toSeq,toSet或toMap。
迭代器相对于集合而言,是一个’懒’的替代品,只有在需要时才去取元素。如果你不需要更多元素,则不会付出计算剩余元素的代价。
但是,迭代器也是很脆弱的。每次对next的调用都会改变迭代器的指向。
流(stream)提供的是一个不可变的替代品。流是一个尾部被懒计算的不可变列表——只有当你需要时,它才会被计算。
def numsFrom(n:BigInt):Stream[BigInt] = n #:: numsFrom(n+1)
#::构建出来的是一个流
val tenOrMore = numsFrom(10)
//得到
Stream(10,?)//流对象,其尾部未被求值
tenOrMore.tail.tail.tail
//得到
Stream(13,?)
//流的方法是懒执行的
val squares = numsFrom(1).map(x=> x*x)
//得到
Stream(1,?)
//需要调用squares.tail来强制对下一个元素求值
//想得到更多答案,调用take,然后用force,强制对所有值求值。如:
squares.take(5).force
//得到
Stream(1,4,9,16,25)
squares.force //别!!
这个调用将尝试对一个无穷流的所有成员进行求值,引发OutOfMemoryError
可以从迭代器构造一个流,如:
Source.getLines方法返回一个Iterator[String]
用这个迭代器,对于每行只能访问一次。而流将缓存访问过的行,允许你重新访问:
val words = Source.fromFile("/usr/share/dict/words").getLines.toStream
words //Stream(A,?)
words(5) //Aachen
words //Stream(A,A's,AOL,AOL's,Aachen,?)
流方法是懒执行的,仅当结果被需要时才计算。你可以对其他集合应用view方法来得到类似的效果。该方法产出一个其方法总是被懒执行的集合。
如:
val powers = (0 until 1000).view.map(pow(10,_))
将产出一个未被求值的集合。(不像流,这里连第一个元素都未被求值)
当执行如下代码时:
powers(100)
pow(10,100)被计算,但其他值的幂并没有被计算。和流不同,这些视图并不缓存任何值。
懒集合对于处理那种需要以多种方式进行变换的大型集合是很有好处的,因为它避免了构建出大型中间集合的需要。
如:
( 0 to 1000).map(pow(10,_)).map(1 / _)
和
( 0 to 1000).view.map(pow(10,_)).map(1 / _).force
略
当你从多个线程访问一个可变集合时,你需要确保自己不会在其他线程正在访问它时对其进行修改。Scala类库提供了6个特质,可以将他们混入集合,让集合的操作变成同步的:
SynchronizedBuffer
SynchronizedMap
SynchronizedPriorityQueue
SynchronizedQueue
SynchronizedSet
SynchronizedStack
如下代码构建出的就是带有同步操作的映射:
val scores = new scala.collection.mutable.HashMap[String,Int] with
scala.collection.mutable.SynchronizedMap[String,Int]
通常,最好使用java.util.concurrent
包中的某个类,如多个线程共享一个映射,那么就用ConcurrentHashMap或ConcurrentSkipListMap,这些集合比简单的用同步方式执行所有方法的映射更为高效。不同线程可以并发的访问数据结构中互不相关的部分。
如果coll是个大型集合,那么:
coll.par.sum 会并发的对它求和
par方法产出当前集合的一个并行实现。
该实现会尽可能的并行的执行集合方法,例如:
coll.par.count(_ % 2 == 0)
将会并行的对偶集合求前提表达式的值,然后将结果组合在一起,得出coll中所有偶数的数量。
并行化for循环:
for(i <- (0 until 100).par) print(i + " ")
//数字是按照作用于该任务的线程产出的顺序输出的
for(i <- (0 until 100).par) yield i + " "
//结果是依次组装的
par方法返回的并行集合的类型为扩展自ParSeq,ParSet或ParMap特质的类型,所有这些特质都是ParIterable的子类型。这些并不是Iterable的子类型,因此你并不能将并行集合传递给预期Iterable,Seq,Set或Map的方法你可以用ser方法将并行集合转换回串行集合,也可以实现接受通用的GenIterable,GenSeq, GenSet或GenMap类型的参数的方法。