快學(xué)Scala第13章----集合

本章要點

  • 所有集合都擴展自Iterable特質(zhì)
  • 集合有三大類:序列、集柴底、映射
  • 對于幾乎所有集合類笼踩,Scala都同時提供了可變的和不可變的版本
  • Scala列表要么是空的,要么擁有一頭一尾绑蔫,其中尾部本身又是一個列表
  • 集是無先后次序的集合
  • 用LinkedhashSet 來保留插入順序,或者用SortedSet來按順序進行迭代
  • ‘+’ 將元素添加到無先后次序的集合中泵额; +: 和 :+ 向前或向后追加到序列配深; ++將兩個集合串接在一起; -和--移除元素
  • Iterable和Seq特質(zhì)有數(shù)十個用于常見操作的方法
  • 映射嫁盲、折疊和拉鏈操作是很有用的技巧篓叶,用來將函數(shù)或操作應(yīng)用到集合中的元素

主要的集合特質(zhì)

Scala集合繼承層級中的關(guān)鍵特質(zhì):


jihe.png

Iterable值的是那些能申城用來訪問集合中所有元素的Iterator的集合,類似于C++的迭代器羞秤。

val coll = ...  //某種Iterable
val iter = coll.iterator
while (iter.hasNext) {
  對iter.next() 執(zhí)行某種操作
}

Seq是一個有先后次序的值的序列缸托,例如數(shù)字或列表。IndexedSeq允許我們通過使用下標的方式快速訪問元素瘾蛋。
Set是一組沒有先后次序的值的集合俐镐。在SortedSet中,元素是排序的瘦黑。
Map是一組(key, value)對偶京革。 SortedMap按照鍵的排序的。
每個Scala集合特質(zhì)或類都有一個帶有apply方法的伴生對象幸斥,這個apply方法可以用來構(gòu)建該集合的實例匹摇,而不用使用new,這樣的設(shè)計叫做"統(tǒng)一創(chuàng)建原則"甲葬。


可變和不可變集合

Scala同時支持可變和不可變的集合廊勃。Scala優(yōu)先采用不可變集合,因此你可以安全的共享其引用经窖。任何對不可變集合的修改操作都返回的是一個新的不可變集合坡垫,它們共享大部分元素。

def digits(n: Int): Set[Int] = {
  if (n < 0) digits(-n)
  else if (n < 10) Set(n)
  else digits(n / 10) + (n % 10)
}

這個例子利用遞歸不斷的產(chǎn)生新的集合画侣,但是要注意遞歸的深度冰悠。


序列

最重要的不可變序列:


seq.png

Vector是ArrayBuffer的不可變版本,和C++的Vector一樣配乱,可以通過下標快速的隨機訪問溉卓。而Scala的Vector是以樹形結(jié)構(gòu)的形式實現(xiàn)的皮迟,每個節(jié)點可以有不超過32個子節(jié)點。這樣對于有100萬的元素的向量而言桑寨,只需要4層節(jié)點。
Range表示一個整數(shù)序列尉尾,例如0,1,2,3,4,5 或 10,20,30 . Rang對象并不存儲所有值而只是起始值、結(jié)束值和增值辨图。

最有用可變序列:


seq2.png

列表

在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

:: 操作符從給定的頭和尾創(chuàng)建列表:

9 :: List(4, 2)  // List(9,4,2)
// 等同于
9 :: 4 :: 2 ::  Nil   // 這里是又結(jié)合的

遍歷鏈表:可以使用迭代器杉女、遞歸或者模式匹配

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
}

// 去除每兩個中的一個
var cur = lst
while (cur != Nil && cur.next != Nil) {
  cur.next = cur.next.next
  cur = cur.next
}

**注意: **如果你想要將列表中的某個節(jié)點變成列表的最后一個節(jié)點,你不能夠?qū)ext引用設(shè)為Nil熏挎,而應(yīng)該將next引用設(shè)為LinkedList.empty坎拐。也不要設(shè)為null,不然在遍歷該鏈表時會遇到空指針錯誤都伪。


集是不重復(fù)的元素的集合积担,與C++中的set相同。集并不保留元素插入的順序帝璧,默認情況下的烁,集以哈希集實現(xiàn)。
而鏈式哈希集可以記住元素插入的順序铃芦,它會維護一個鏈表來達到這個目的。

val weekdays = scala.collection.mutable.LinkedHashSet("Mo", "Tu", "We", "Th", "Fr")

對于SortedSet已排序的集使用紅黑樹實現(xiàn)的漓穿。Scala沒有可變的已排序的集注盈,前面已經(jīng)講過。
集的一些常見操作:

val digits = Set(1,7,2,9)
digits contains 0   // false
Set(1, 2) subsetOf digits  // true

val primes = Set(2,3,5,7)
digits union primes  // Set(1,2,3,5,7,9)
// 等同于
digits | primes  // 或 digits ++ primes

digits intersect primes  // Set(2僚饭, 7)
// 等同于
digits & primes

digits diff primes  // Set(1, 9)
// 等同于
digits -- primes

用于添加或去除元素的操作符

option.png

一般而言胧砰, + 用于將元素添加到無先后次序的集合尉间,而+: 和 :+ 則是將元素添加到有先后次序的集合的開頭或是結(jié)尾。

Vector(1,2,3) :+ 5  // Vector(1,2,3,5)
1 +: Vector(1,2,3)  // Vector(1,1,2,3)

常用方法

Iterable特質(zhì)最重要的方法:


Iterable.png
Iterable2.png

Seq特質(zhì)在Iterable特質(zhì)的基礎(chǔ)上又增加的一些方法:

seq.png
seq2.png

將函數(shù)映射到集合

map方法可以將某個函數(shù)應(yīng)用到集合的每一個元素并產(chǎn)出其結(jié)果的集合。例如:

val names = List("Peter", "Paul", "Mary")
names.map(_.toUpperCase)   // List("PETER", "PAUL", "MARY")
// 等同于
for (n <- names) yield n.toUpperCase

如果函數(shù)產(chǎn)出一個集合而不是單個值得話画切,則使用flatMap將所有的值串接在一起囱怕。例如:

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", "paul", "MARY", "mary")

collect方法用于偏函數(shù)---并沒有對所有可能的輸入值進行定義的函數(shù)娃弓。例如:

"-3+4".collect {case '+' => 1; case '-' => -1}    // Vector(-1,1)
"-3+4".collect {case '-' => -1}    // Vector(-1)
"-3+4".collect {case '+' => 1}    // Vector(1)
"-3+4".collect {case '*' => 1}    // Vector()

foreach方法將函數(shù)應(yīng)用到各個元素但不關(guān)心函數(shù)的返回值。

names.foreach(println)

化簡钝计、折疊和掃描

reduceLeft齐佳、reduceRight、foldLeft本鸣、foldRight硅蹦、scanLeft闷煤、scanRight方法將會用二元函數(shù)來組合集合中的元素:

List(1,7,2,9).reduceLeft(_ - _)  // ((1 - 7) - 2) - 9
List(1,7,2,9).reduceRight(_ - _)  // 1 - (7 - (2 - 9))

List(1,7,2,9).foldLeft(0)(_ - _)   // 0 - 1 -7 - 2 - 9
List(1,7,2,9).foldRight(0)(_ - _)  // 1 - (7 - (2 - (9 - 0)))

(1 to 10).scanLeft(0)(_ + _)  // Vector(0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55)

拉鏈操作

前面的章節(jié)已經(jīng)講過拉鏈操作鲤拿。除了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, 1), (4.3, 1))

"Scala".zipWithIndex   // Vector(('S', 0), ('c', 1), ('a', 2), ('l', 3), ('a', 4) )

迭代器

迭代器的好處就是你不用將開銷很大的集合全部讀進內(nèi)存宁否。例如讀取文件操作,Source.fromFile產(chǎn)出一個迭代器饱须,使用hasNext和next方法來遍歷:

while (iter.hasNext)
  對 iter.next() 執(zhí)行某種操作

這里要注意迭代器多指向的位置台谊。在調(diào)用了map、filter督怜、count、sum丰歌、length等方法后屉凯,迭代器將位于集合的尾端,你不能再繼續(xù)使用它晓勇。而對于其他方法而言灌旧,比如find或take,迭代器位于已經(jīng)找到元素或已取得元素之后描融。


流是一個尾部被懶計算的不可變列表-----也就是說衡蚂,只有當(dāng)你需要時它才會被計算。

def numsFrom(n: BigInt): Stream[BigInt] = n #:: numsFrom(n + 1)
val tenOrMore = numsFrom(10)  // 得到一個 Stream(10, ?) 流對象年叮,尾部未被求值
temOrMore.tail.tail.tail   // Stream(13, ?)

流的方法是懶執(zhí)行的只损。例如:

val squares = numsFrom(1).map(x => x * x)   // 產(chǎn)出 Stream(1, ?)
// 需要調(diào)用squares.tail來強制對下一個元素求值
squares.tail   // Stream(4, ?)

// 使用take和force強制求指定數(shù)量的值
squares.take(5).force  // Stream(1,4,9,16,25)

**注意: ** 不要直接使用 squares.force, 這樣將會是一個無窮的流的所有成員求值啸蜜, 引發(fā)OutOfMemoryError 辈挂。


懶視圖

應(yīng)用view方法也可以實現(xiàn)懶執(zhí)行,該方法產(chǎn)出一個其方法總是被懶執(zhí)行的集合蜂林。例如:

val powers = (0 until 1000).view.map(pow(10, _))
powers(100)  //pow(10, 100)被計算噪叙,其他值的冪沒有被計算

和流不同霉翔,view連第一個元素都不求值,除非你主動計算子眶。view不緩存任何值序芦,每次調(diào)用都要重新計算。
懶集合對于處理以多種方式進行變換的大型集合很有好處渴杆,因為它避免了構(gòu)建出大型中間集合的需要宪塔。例如:

(0 to 1000).map(pow(10, _)).map(1 / _)  // 1
(0 to 1000).view.map(pow(10, _)).map(1 / _).force  // 2

第一個式子 會產(chǎn)出兩個集合蝌麸,第一個集合的每一個元素是pow(10, n),第二個集合是第一個集合中每個集合中的元素取倒數(shù)敢辩。 而第二個表達式使用了視圖view,當(dāng)動作被強制執(zhí)行時盗冷,對每個元素同廉,這兩個操作是同時執(zhí)行的,不需要額外構(gòu)建中間集合锅劝。


與Java集合的互操作

java.png
scala.png

線程安全的集合

Scala類庫提供了六個特質(zhì)故爵,你可以將它們混入集合隅津,讓集合的操作變成同步:
SynchronizedBuffer
SynchronizedMap
SynchronizedPriorityQueue
SynchronizedQueue
SynchronizedSet
SynchronizedStack

例如:

val scores = new scala.collection.mutable.HashMap[String, Int] with scala.collection.mutable.SynchronizedMap[String, Int]

當(dāng)然伦仍,還有更高效的集合,例如ConcurrentHashMap或ConcurrentSkipListMap隧枫,比簡單的用同步方式執(zhí)行所有的方法更為有效谓苟。


并行集合

集合的par方法產(chǎn)出當(dāng)前集合的一個并行實現(xiàn),例如sum求和,多個線程可以并發(fā)的計算不同區(qū)塊的和纱皆,在最后這部分結(jié)果被匯總到一起芭商。

coll.par.sum

你可以通過對要遍歷的集合應(yīng)用par并行for循環(huán)

for(i <- (0 until 100).par) print(i + " ")
par.png

而在 for/yield循環(huán)中铛楣,結(jié)果是依次組裝的:

for(i <- (0 until 100).par) yield i + " "
yield.png

這里要注意變量是共享變量簸州,還是循環(huán)內(nèi)的局部變量:

var count = 0
for (c <- coll.par) {if (c % 2 == 0) count += 1}  // error

**注意: **par方法返回的并行集合的類型為擴展自ParSeq歧譬、ParSet或ParMap特質(zhì)的類型搏存,所有這些特質(zhì)都是ParIterable的子類型璧眠。這些并不是Iterable的子類型,因此你不能將并行集合傳遞給預(yù)期Iterable袁滥、Seq灾螃、Set、Map方法藐握。你可以用ser方法將并行集合轉(zhuǎn)換回串行集合垃喊,也可以實現(xiàn)接受通用的GenIterable本谜、GenSeq、GenSeq溜在、GenMap類型的參數(shù)的方法他托。

**說明: **并不是所有的方法都可以被并行化。例如reduceLeft志笼、reduceRight要求每個操作符按照順序先后被應(yīng)用把篓。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末韧掩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子坊谁,更是在濱河造成了極大的恐慌,老刑警劉巖敌买,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虹钮,死亡現(xiàn)場離奇詭異膘融,居然都是意外死亡,警方通過查閱死者的電腦和手機春畔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門律姨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來臼疫,“玉大人烫堤,你說我怎么就攤上這事「胝澹” “怎么了富蓄?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長躏吊。 經(jīng)常有香客問我帐萎,道長疆导,這世上最難降的妖魔是什么葛躏? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮败富,結(jié)果婚禮上兽叮,老公的妹妹穿的比我還像新娘。我一直安慰自己账阻,他們只是感情好泽本,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布规丽。 她就那樣靜靜地躺著,像睡著了一般冰抢。 火紅的嫁衣襯著肌膚如雪雄嚣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機與錄音港谊,去河邊找鬼歧寺。 笑死,一個胖子當(dāng)著我的面吹牛龙致,可吹牛的內(nèi)容都是我干的顷链。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼榛了,長吁一口氣:“原來是場噩夢啊……” “哼霜大!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起曙强,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤旗扑,失蹤者是張志新(化名)和其女友劉穎慈省,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體袱衷,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡致燥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年排截,在試婚紗的時候發(fā)現(xiàn)自己被綠了断傲。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡箱蝠,死狀恐怖垦垂,靈堂內(nèi)的尸體忽然破棺而出劫拗,到底是詐尸還是另有隱情,我是刑警寧澤页慷,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布因妇,位于F島的核電站找蜜,受9級特大地震影響稳析,放射性物質(zhì)發(fā)生泄漏彰居。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一畦徘、第九天 我趴在偏房一處隱蔽的房頂上張望抬闯。 院中可真熱鬧溶握,春花似錦、人聲如沸睡榆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽勿负。三九已至劳曹,卻和暖如春铁孵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背檀头。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工暑始, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人廊镜。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓嗤朴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親股缸。 傳聞我的和親對象是個殘疾皇子吱雏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容