對映和元組 |
摘要:
一個經典的程式設計師名言是:"如果只能有一種資料結構,那就用雜湊表吧"。雜湊表或者更籠統地說對映,是最靈活多變的資料結構之一。對映是鍵/值對偶的集合。Scala有一個通用的叫法:元組,即n個物件的聚集,並不一定要相同型別的。對偶不過是一個 n=2的元組,元組對於那種需要將兩個或更多值聚集在一起時特別有用。本篇的要點包括:
01. Scala有十分易用的語法來建立、查詢和遍歷對映。
02. 你需要從可變的和不可變的對映中做出選擇。
03. 預設情況下,你得到的是一個雜湊對映,不過你也可以指明要樹形對映。
04. 你可以很容易地在Scala對映和Java對映之間來回切換。
05. 元組可以用來聚集值。
構造對映 |
不可變對映
我們可以這樣構造一個對映:
val scores = Map ("Alice"-> 10, "Bob"->3, "Cindy"->8)
上述程式碼構造出一個不可變的Map[String,Int],其值不能被改變。
可變對映
如果你想要一個可變對映,則用
val scores = scala.collection.mutable.Map("Alice"-> 10, "Bob"->3, "Cindy"->8)
如果想從—個空的對映開始,你需要選定一個對映實現並給出型別引數:
val scores = new scala.collection.mutable.HashMap [String, Int]
在Scala中,對映是對偶的集合。對偶簡單地說就是兩個值構成的組,這兩個值並不一定是同一個型別的,比如("Alice",10)
操作符建立對偶
操作符用來建立對偶:
"Alice"->10
上述程式碼產出的值是:
("Alice", 10)
完全也可以用下面這種方式來定義對映:
val scores=Map ( ("Alice", 10), ("Bob", 3), ("Cindy", 8) )
只不過->操作符看上去比圓括號更易讀那麼一點,也更加符合大家對對映的直觀感覺:對映這種資料結構是一種將鍵對映到值的函式。區別在於通常的函式計算值,而對映只是做查詢。
獲取對映中的值 |
在Scala中,函式和對映之間的相似性尤為明顯,因為你將使用()表示法來查詢某個鍵對應的值:
val bobsScore = scores ("Bob") //類似於Java中的scores.get ("Bob")
如果對映並不包含請求中使用的鍵,則會丟擲異常。要檢查對映中是否有某個指定的鍵,可以用contains方法:
val bobsScore = if (scores.contains ("Bob")) scores("Bob") else 0
由於這樣的組合呼叫十分普遍,以下是一個快捷寫法:
val bobsScore = scores.getOrElse("Bob", 0) // 如果對映包含鍵"Bob",返回對應的值;否則,返回0
最後,對映.get(鍵)這樣的呼叫返回一個Option物件,要麼是Some(鍵對應的值),要麼是None
更新鍵值 |
更新可變對映
在可變對映中,你可以更新某個對映的值,或者新增一個新的對映關係,做法是在=號的左側使用():
scores ("Bob") = 10 // 更新鍵"Bob"對應的值
scores ("Fred")=7 // 增加新的鍵/值對偶到scores
或者,你也可以用+=操作來新增多個關係:
scores+= ("Bob"-> 10, "Fred"->7)
要移除某個鍵和對應的值,使用-=操作符:
scores -= "Alice"
不可變對映
雖然不能更新一個不可變的對映,但你可以做一些同樣有用的操作,即獲取一個包含所需要的更新的新對映:
val newScores = scores+ ("Bob"->10,"Fred"->7) // 更新過的新對映
newScores對映包含了與scores相同的對映關係,此外"Bob"被更新,"Fred"被新增了進來
除了把結果作為新值儲存外,你也可以更新Var變數:
var scores = …
scores = scores+ ("Bob"->10, "Fred"->7)
同理,要從不可變對映中移除某個鍵,你可以用一操作符來獲取一個新的去掉該鍵的對映:
scores = scores - "Alice"
你可能會覺得這樣不停地建立新對映效率很低,不過事實並非如此。老的和新的對映共享大部分結構。這樣做之所以可行,是因為它們是不可變的。
迭代對映 |
如下這段超簡單的迴圈即可遍歷對映中所有的鍵/值對偶:
for ((k,v) <- 對映) 處理k和v
這裡的"魔法"是你可以在Scala的for迴圈中使用模式匹配。這樣一來,不需要冗雜的方法呼叫,你就可以得到每一個對偶的鍵和值
如果出於某種原因,你只需要訪問鍵或值,像Java一樣,則可以用keySet和values方法。values方法返回—個Iterable,你可以在for迴圈當中使用這個Iterable
scores.keySet // 一個類似Set("Bob","Cindy","Fred","Alice")這樣的集合
for ( v <- scores.values) printlnI(v) // 將列印10 8 7 10或其他排列組合
要反轉一個對映,即交換鍵和值的位置,可以用:
for ( (k,v) <- 對映) yield (v.k)
已排序對映 |
在操作對映時,你需要選定一個實現:一個雜湊表或者一個平衡樹。預設情況下,Scala給的是雜湊表。由於對使用的鍵沒有很好的雜湊函式,或者需要順序地訪問所有的鍵,因此,你可能會想要一個樹形對映。
要得到一個不可變的樹形對映而不是雜湊對映的話,可以用:
val scores = scala.collections.immutable.SortedMap("Alice"->10,"Fred"->7, "Bob"->3,"Cindy"->8)
很可惜,Scala( 2.9)並沒有可變的樹形對映。如果那是你要的,最接近的選擇是使用Java的TreeMap。需要注意的是:如果要按插入順序訪問所有鍵,使用LinkedHashMap,例如:
val months = scala.collection.mutable.LinkedHashMap("January" -> 1,"February" -> 2, "March" -> 3, …)
與Java互操作 |
Java到Scala
如果你通過Java方法呼叫得到了一個Java對映,你可能想要把它轉換成一個Scala對映,以便使用更便捷的Scala對映API。這對於需要操作Scala,並未提供的可變樹形對映的情況也很有用。只需要增加如下引入語句:
import scala.collection.JavaConversions .mapAsScalaMap
然後通過指定Scala對映類型來觸發轉換:
val scores:scala.collection.mutable.Map[String, Int]=new java.util.TreeMap[String, Int]
除此之外,你還可以得到從java.util.Properties到Map[String,String]的轉換:
import scala.collection.JavaConversions.propertiesAsScalaMap
val props:Scala.collection.Map[String, String] = System.getProperties()
Scala到Java
反過來講,要把Scala對映傳遞給預期Java對映的方法,提供相反的隱式轉換即可
例如:
import scala.collection.JavaConversions.mapAsjavaMap
import java.awt.font.TextAttribute. _ // 引入下面的對映會用到的鍵
val attrs = Map ( FAMILY -> "Serif", SIZE -> 12 ) // Scala對映
val font=new java.awt.Font (attrs) // 該方法預期一個Java對映
元組 |
元組定義
對映是鍵/值對偶的集合。對偶足元組( tuple)的最簡單形態,元組是不同型別的值的聚集。元組的值是通過將單個的值包含在圓括號中構成的。例如:
(1, 3.14, "Fred")
是一個元組,型別為:
Tuple3 [Int, Double, java.lang.String]
型別定義也可以寫為:
(Int, Double, java,lang.String)
如果你有一個元組,比如:
val t = (1,3.14, "Fred")
你就可以用方法_1、_2、_3訪問其組元,比如:
val second = t._2 // 將second設為3.14
需要注意的是:和陣列或字串中的位置不同,元組的各組元從1開始,而不是0。你可以把t._2寫為t _2,即用空格而不是句點,但不能寫成t_2
獲取元組
通常,使用模式匹配來獲取元組的組元,例如:
val (first,second,third)=t //將first設為1,second設為3.14.third設為"Fred"
如果並不是所有的部件都需要,那麼可以在不需要的部件位置上使用_:
val (first, second, _ ) = t
元組可以用於函式需要返回不止一個值的情況。舉例來說,StringOps的partition方法返回的是一對字串,分別包含了滿足某個條件和不滿足該條件的字元:
"New York".partition ( _.isUpper) //輸出對偶( "NY","ew ork")
拉鍊操作 |
使用元組的原因之一是把多個值綁在一起,以便它們能夠被一起處理,這通常可以用zip方法來完成。舉例來說,下面的程式碼:
val symbols = Array ("<","-",">")
val counts = Array (2, 10, 2)
val pairs = symbols.zip(counts)
輸出對偶的陣列:
Array ( ("<",2),("-",10), (">",2) )
然後這些對偶就可以被一起處理:
for ( (s,n) <- pairs ) Console.print(s*n) // 會列印<<---------->>
需要注意的是:用toMap方法可以將對偶的集合轉換成對映。如果你有一個鍵的集合,以及一個與之平行對應的值的集合,那麼你就可以用拉鍊操作將它們組合成一個對映:keys.zip(values) .toMap
如果,您認為閱讀這篇部落格讓您有些收穫,不妨點選一下右下角的【推薦】。
如果,您希望更容易地發現我的新部落格,不妨點選一下左下角的【關注我】。
如果,您對我的部落格所講述的內容有興趣,請繼續關注我的後續部落格,我是【Sunddenly】。本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。