scala的集合基础

seq set map都是继承iterable,而iterable指的是那些能生成用来访问集合中的所有元素的的iterator的集合:

val coll = ...//某种iterable

val iter = coll.iterator

while(iter.hasNext)

对iter.next()执行某种操作

这是遍历一个集合最基本的方式。

seq是一个有先后次序的值的序列,比如数组或列表。indexedseq允许我们通过整型下标快速的访问任意元素。举例来说,arraybuffer是带下标的,但链表不是。

set是一组没有先后次序的值,在sortedset中,元素以某种排过序的顺序被访问。

map是一组(键,值)对偶。sortedmap按照键的排序访问其中的实体。

这个继承层级和java很相似,同时有一些不错的改进:

1 映射隶属于同一个继承层级而不是一个单独的层级关系。

2indexedseq是数组的超类型,但不是列表的超类型,以便区分。

每个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.mutable.Map,也有scala.collection.immutable.Map。它们有一个共同的超类型scala.collection.Map(当然了,这个超类型没有定义任何改值操作)。

当你握有一个指向scala.collection.immutable.Map的引用时,你知道没有人能修改这个映射。如果你有的是一个scala.collection.Map,那么你不能改变它,但别人也许会。

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得到可变的映射。

不可变集合的关键在于你可以基于老的集合创建新的集合。举例来说,如果numbers是一个不可变的集,那么numbers + 9 就是一个包含了numbers和9的新集。如果9已经在集中,则你得到的是指向老集的引用。这在递归计算中特别自然。举例来说,我们可以计算某个整数中所有出现过的阿拉伯数字的集:

def digits(n: Int): Set[Int] = 

if (n < 0) digits(-n)

else if (n < 10) Set(n)

else digit(n / 10) + (n % 10)

这个方法从包含单个数字的集开始,每一步,添加进另外一个数字,不过,添加某个数字并不会改变原有的集合,而是构造出一个新的集。

序列

不可变序列:indexedseq list stream stack queue都继承了seq,而vector range继承了indexedseq

vector是arraybuffer的不可变版本,一个带有下标的序列,支持快速随机访问。向量是以树形结构的形式实现的,每个节点可以有不超过32个子节点。对于一个有100万个元素的向量而言,我们只需要四层节点,因为10的6次方约等于32的四次方。访问这样一个列表中的某个越苏只需要4跳,而在链表中,同样的操作平均需要500000跳。

range表示一个整数序列,比如0,1,2,3,4,5。当然了,range对象并不存储所有值而只是起始值,结束值和增值。你可以用to和until方法来构造range对象。

可变序列:indexedseq stack queue priorityQueue LinkedList DoubleLinkedList继承seq  而arraybuffer继承indexedseq

列表

在scala中,列表要么是Nil(即空表),要么是一个head元素加一个tail,而tail又是一个列表。比如下面一个列表 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,4,2)你也可以将这个列表写做 9 :: 4 ::2 :: Nil

注意::是右结合的。通过::操作符,列表将从末端开始构建。

9 :: (4 :: (2 :: Nil)))

在java或c++中,我们用迭代器来遍历链表。在scala中你也可以这样做,但使用递归会更加自然。例如如下函数计算整数链表中所有元素的和:

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类库。它已经有sum方法了:List(9, 4. 2).sum //输出15

可变列表

可变的LinkedList和不可变的List相似,只不过你可以通过对elem引用赋值来修改其头部,对next引用赋值来修改其尾部。

注意,你并不是给head和tail赋值

举例来说,如下循环将把所有负值都改成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

}

如下循环将去除每两个元素中的一个:

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")

如果你想要按照已排序的顺序来访问集中的元素,用已排序的集:

scala.collection.immutable.SortedSet(1, 2, 3, 4, 5, 6)

已排序的集是用红黑树实现的。

注意scala2.9没有可变的已排序集,如果你需要这样一个数据结构,可以用java.util.TreeSet。

位集(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写做--。

用于添加或去除元素的操作符有很多,一般来说,+用来将元素添加到无先后次序的集合,而+:和:+则是将元素添加到有先后次序的集合的开头或末尾

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//在这里我们没法用+=,因为向量没有+操作。

要移除元素,使用-操作符:

Set(1,2,3) - 2//将产出Set(1, 3)

你也可以用++来一次添加多个元素:

coll ++ coll2

将产出一个与coll类型相同,且包含了coll和coll2中所有元素的集合。类似的,--操作符将一次移除多个元素。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值