1. 程式人生 > >scala的集合基礎

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中所有元素的集合。類似的,--操作符將一次移除多個元素。