本文Scala使用的版本是2.11.8
作為值得函數
import scala.math._val num = 3.14// ceil函數后的_表示這是個函數,而不是忘記傳參val fun = ceil _// 調用fun(num)// 傳遞Array(3.14, 1.42, 2.0).map(fun)匿名函數
// 存放到變量val triple = (x: Double) => 3 * x// 傳遞給另一個函數Array(3.14, 1.42, 2.0).map((x: Double) => 3 * x)定義接收函數參數的函數
def valueAtOneQuarter(f: (Double) => Double) = f(0.25)// 類型為((Double) => Double) => Double// 也可以產生另一個函數def mulBy(factor: Double) = (x: Double) => factor * x// 類型為(Double) => ((Double) => Double)參數類型推斷
Scala會盡可能幫助推斷類型信息。例如,如果參數在=>右側只出現一次,可以用_替換它。
valueAtOneQuarter(3 * _)一些有用的高階函數
函數名 | 描述 |
---|---|
map | 將一個函數應用到某個集合的所有元素并返回結果 |
foreach | 將函數應用到每個元素,無返回值 |
filter | 輸出所有匹配某個特定條件的元素 |
reduceLeft | 接受一個二元的函數,并將它應用到序列中的所有元素,從左到右 |
sortWith | 接受一個二元函數,進行排序 |
閉包
def mulBy(factor: Double) = (x: Double) => factor * xval triple = mulBy(3)val half = mulBy(0.5)PRintln(triple(14) + " " + half(14)) // 將打印42 7每一個返回的函數都有自己的factor設置。這樣的函數被稱作閉包(closure)。閉包由代碼和代碼用到的任何非局部變量定義構成。
這些函數實際上是以類的對象方式實現的。
在Scala中,每當想要告訴另一個函數做某件事時,會傳一個函數參數給它。而java是將動作放在一個實現某接口的類中,然后將該類的一個實例傳遞給另一個方法。
這些接口被稱做SAM類型(single abstract method)。
Java實現
import java.awt.event.{ActionEvent, ActionListener}import javax.swing.{JButton, JFrame}object Note1 { implicit def makeAction(action: (ActionEvent) => Unit) = new ActionListener { override def actionPerformed(event: ActionEvent) { action(event) } } def main(args: Array[String]) { var data = 0 val frame = new JFrame("SAM Testing"); val jButton = new JButton("Counter") jButton.addActionListener(new ActionListener { override def actionPerformed(event: ActionEvent) { data += 1 println(data) } }) }}Scala實現
import java.awt.event.{ActionEvent, ActionListener}import javax.swing.{JButton, JFrame}object Note1 { // 定義隱式轉換,把函數轉換成一個ActionListener實例 implicit def makeAction(action: (ActionEvent) => Unit) = new ActionListener { override def actionPerformed(event: ActionEvent) { action(event) } } def main(args: Array[String]) { var data = 0 val frame = new JFrame("SAM Testing"); val jButton = new JButton("Counter") // 傳遞函數參數 jButton.addActionListener((event: ActionEvent) => { data += 1; println(data) }) frame.setContentPane(jButton); frame.pack(); frame.setVisible(true); }}柯里化(currying)指的是將原來接受兩個參數的函數變成新的接受一個參數的函數的過程。新的函數返回一個以原有第二個參數做為參數的函數。
def mulOneAtATime(x: Int) = (y: Int) => x * y// 調用mulOneAtATime(6)(7)// Scala支持如下簡寫來定義這樣的柯里化函數def mulOneAtATime(x: Int)(y: Int) = x * y示例
val a = Array("Hello", "World")val b = Array("hello", "world")a.corresponds(b)(_.equalsIgnoreCase(_))通過上面這樣的應用,可以構建控制抽象:看上去像是編程語言的關鍵字的函數。
示例:實現像while語句的函數
object Note2 { // 函數參數的專業術語叫換名調用參數, 和常規的參數不同, 函數在被調用時, // 參數表達式不會被求值, 如下 x == 0 def until(condition: => Boolean) (block: => Unit): Unit = { if (!condition) { block until(condition)(block) } } def main(args: Array[String]): Unit = { var x = 10 until (x == 0) { x -= 1 println(x) } }}1. 編寫函數values(fun: (Int) => Int, low: Int, high: Int),該函數輸出一個集合,對應給定區間內給定函數的輸入和輸出。比如,values(x => x * x, -5, 5)應該產出一個對偶的集合(-5, 25), (-4, 16), (-3, 9), …, (5, 25)
object One { def values(fun: (Int) => Int, low: Int, high: Int) = { var array = List[(Int, Int)]() low to high foreach { x => array = array :+ (x, fun(x)) } array } def main(args: Array[String]) { println(values(x => x * x, -5, 5).mkString(" ")) }}// 結果(-5,25) (-4,16) (-3,9) (-2,4) (-1,1) (0,0) (1,1) (2,4) (3,9) (4,16) (5,25)2. 如何用reduceLeft得到數組中的最大元素?
object Two { def main(args: Array[String]) { val arr = Array(1, 333, 4, 6, 4, 4, 9, 32, 6, 9, 0, 2) val max = arr.reduceLeft((x, y) => { if (x > y) x else y }) println(max) }}// 結果3333. 用to和reduceLeft實現階乘函數,不得使用循環或遞歸
object Three { def factorial(n: Int): Int = { if (n > 0) { 1 to n reduceLeft (_ * _) } else if (n == 0) { 1 } else { throw new IllegalArgumentException("請輸入非負數.") } } def main(args: Array[String]) { println(factorial(-1)) }}// 結果1204. 前一個實現需要處理一個特殊情況,即n<1的情況。展示如何用foldLeft來避免這個需要。(在scaladoc中查找foldLeft的說明。它和reduceLeft很像,只不過所有需要結合在一起的這些值的首值在調用的時候給出。)
object Four { def factorial(n: Int): Int = { if (n < 0) { throw new IllegalArgumentException("請輸入非負數.") } else { (1 to n).foldLeft(1)(_ * _) } } def main(args: Array[String]) { println(factorial(0)) }}5. 編寫函數largest(fun: (Int) => Int, inputs: Seq[Int]),輸出在給定輸入序列中給定函數的最大值。舉例來說,largest(x => 10 * x - x * x, 1 to 10)應該返回25。不得使用循環或遞歸
object Five { def largest(fun: (Int) => Int, inputs: Seq[Int]): Int = { inputs.map(fun).max } def main(args: Array[String]) { println(largest(x => 10 * x - x * x, 1 to 10)) }}6. 修改前一個函數,返回最大的輸出對應的輸入。舉例來說,largestAt(fun: (Int) => Int, inputs: Seq[Int])應該返回5。不得使用循環或遞歸
object Six { def largestAt(fun: (Int) => Int, inputs: Seq[Int]): Int = { inputs.map(x => (x, fun(x))) .reduceLeft((x, y) => if (x._2 > y._2) x else y)._1 } def main(args: Array[String]) { println(largestAt(x => 10 * x - x * x, 1 to 10)) }} 7. 要得到一個序列的對偶很容易,比如: val pairs = (1 to 10) zip (11 to 20)
假定你想要對這個序列做某種操作,比如,給對偶中的值求和,但是你不能直接使用: pairs.map( + )
函數 _ + _ 接受兩個Int作為參數,而不是(Int, Int)對偶。編寫函數adjustToPair,該函數接受一個類型為(Int, Int) => Int的函數作為參數,并返回一個等效的, 可以以對偶作為參數的函數。舉例來說就是:adjustToPair(_ * _)((6, 7))應得到42。然后用這個函數通過map計算出各個對偶的元素之和
8. 在12.8節中,你看到了用于兩組字符串數組的corresponds方法。做出一個對該方法的調用,讓它幫我們判斷某個字符串數組里的所有元素的長度是否和某個給定的整數數組相對應
object Eight { def main(args: Array[String]) { val a = Array("asd", "df", "abcd") val b = Array(3, 2, 4) val c = Array(3, 2, 1) println(a.corresponds(b)(_.length == _)) println(a.corresponds(c)(_.length == _)) }}// 結果truefalse9. 不使用柯里化實現corresponds。然后嘗試從前一個練習的代碼來調用。你遇到了什么問題?
沒有柯里化則不能使用前一個練習里的代碼方式來調用10. 實現一個unless控制抽象,工作機制類似if,但條件是反過來的。第一個參數需要是換名調用的參數嗎?你需要柯里化嗎?
// 需要換名和柯里化object Ten { def unless(condition: => Boolean)(block: => Unit) { if (!condition) { block } } def main(args: Array[String]) { unless(0 > 1) { println("Unless!") } }}所有的集合都擴展自Iterable。有三類集合,分別是Seq、Set和Map。
Iterable提供了遍歷一個集合的基本方式。
每個集合特質或類都有一個帶有apply方法的伴生對象,可以用于構建該集合中的實例。
Seq
Seq是一個有先后次序的序列,比如數組和列表。IndexedSeq是它的子特質,允許通過下標訪問元素。
Set
Set是沒有次序的一組值,子特質SortedSet的元素則以某種排過序的順序被訪問。
Map
Map是一組對偶。SortedMap按照鍵的排序訪問其中的實體。
可變和不可變集合
不可變的集合從不改變,因此可以安全地共享其引用。
不可變序列
Vector是ArrayBuffer的不可變版本:可下標訪問,支持快速隨機訪問。它是以樹形結構的形式實現的,每個節點可以有不超過32個子節點。
Range表示一個整數序列,它并不存儲所有值,而只是起始值、結束值和增值。
可變序列
不可變列表
列表要么是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列表遍歷,可以使用迭代器、遞歸或者模式匹配。下面是模式匹配示例:
def sum(lst: List[Int]): Int = lst match { case Nil => 0 case h :: t => h + sum(t)}可變列表
可變的LinkedList和不可變List相似。不過,可以通過對elem引用賦值來修改其頭部,對next引用賦值修改其尾部。
還提供了DoubleLinkedList,區別是多帶一個prev引用。
如果要把某個節點變成列表中的最后一個節點,不能將next引用設為Nil,而是設置為LinkedList.empty
集市不重復元素的集合。集并不保留元素插入的順序,缺省以哈希集實現的。
鏈式哈希集可以記住元素被插入的順序。
val weekdays = scala.collection.mutable.LinkedHashSet("Mo", "Tu", "We", "Th", "Fr")已排序的集(用紅黑樹實現):
scala.collection.immutable.SortedSet(1, 2, 3, 4, 5)位集(BitSet)是以一個字位序列的方式存放非負整數。如果集中有i,則第i個字位是1。提供了可變和不可變的實現。
contains方法檢查某個集是否包含給定的值。subsetOf方法檢查某個集是否是另一個集的子集。
val digits = Set(1, 7, 2, 9)digits contains 0 // falseSet(1, 2) subsetOf digits // trueunion、intersect和diff方法執行通常的集操作。也可以寫作|
、&
和&~
。還可以將聯合(union)寫作++
,將差異(diff)寫作--
。
操作符 | 描述 | 集合類型 |
---|---|---|
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 | ArrayBuffer |
一般,+用于將元素添加到無先后次序的集合,而+:和:+則是將元素添加到有先后次序的集合的開頭或末尾。
Vector(1, 2, 3) :+ 5 // Vector(1, 2, 3, 5)1 +: Vector(1, 2, 3) // Vector(1, 1, 2, 3)和其他以冒號結尾的操作符一樣,+:是右結合的。
提示匯總如下
向后(:+)或向前(+:)追加元素到序列當中。添加(+)元素到無先后次序的集合中。用-移除元素。用++和–來批量添加和移除元素。對于列表,優先使用::和:::。改值操作用+=、++=、-=和–=。對于集合,更喜歡++、&和–。盡量不用++:、+=:和++=:。對于列表可以用+:而不是::來保持與其他集合操作的一致性。但有一個例外:模式匹配(case h::t)不認+:操作符。Iterable特質
方法 | 描述 |
---|---|
head、last、headOption、lastOption | 返回第一個或最后一個元素,或者以Option返回 |
tail、init | 返回除第一個或最后一個元素外其余部分 |
length、isEmpty | 返回集合長度;或者,當長度為零時返回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) | 做出一個有所有元素組成的字符串,將給定字符串分別添加到首個元素之前、每個元素之間,以及最后一個元素之后。第二個方法將該字符串追加到字符串構建器當中 |
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) | 返回交集;或者差值 |
reverse | 當前序列反向 |
sorted、sortWith(less)、sortBy(f) | 使用元素本身的大小、二元函數less,或者將每個元素映射成一個帶先后次序的類型的值得函數f,對當前序列進行排序后的新序列 |
premutations、combinations(n) | 返回一個遍歷所有排列或組合(長度為n的子序列)的迭代器 |
collect方法用于偏函數(partial function)
"-3+4".collect { case '+' => 1; case '-' => -1 }// 得到 Vector(-1, 1)foreach
names.foreach(println)主要說明方法用二元函數來組合集合中的元素。
List(1, 7, 3, 9).reduceLeft(_ - _)// 相當于 ((1 - 7) - 2) - 9 = -17List(1, 7, 3, 9).reduceRight(_ - _)// 相當于 1 - (7 - (2 - 9)) = -13// 以不同于集合首元素的初始元素計算通常也很有用List(1, 7, 2, 9).foldLeft(0)(_ - _)// 相當于(((0 - 1) - 7) - 2) - 9// 也可以像下面這樣代替foldLeft操作(0 /: List(1, 7, 2, 9))(_ - _)// 同樣也提供了foldRight或:/// scanLeft和scanRight方法將折疊和映射操作結合在一起scala> (1 to 10).scanLeft(0)(_ + _)res0: scala.collection.immutable.IndexedSeq[Int] = Vector(0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55)scala> (1 to 10).scanRight(0)(_ + _)res1: scala.collection.immutable.IndexedSeq[Int] = Vector(55, 54, 52, 49, 45, 40, 34, 27, 19, 10, 0)拉鏈操作指的是兩個集合,把它們相互對應的元素結合在一起。
val prices = List(5.0, 20.0, 9.95)val quantities = List(10, 2, 1)// zip方法將它們組合成一個對偶的列表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 }// 所有物件的總價((prices zip quantities) map { p => p._1 * p._2 }).sum// 如果元素長度不等,則返回最終元素的個數與較短的一致// zipAll方法可以指定較短列表的缺省值List(5.0, 20.1, 9.4).zipAll(List(10, 2), 1.2, 1)// 其中zipAll第二個參數用于填充前面的列表;而第三個參數填充后面的列表// zipWithIndex返回對偶列表,其中每個對偶中第二個組成部分是每個元素的下標"Scala".zipWithIndexres4: scala.collection.immutable.IndexedSeq[(Char, Int)] = Vector((S,0), (c,1), (a,2), (l,3), (a,4))可以用iterator方法從集合獲得一個迭代器。
對于完整構造需要很大開銷的集合而言,迭代器很有用。
Source.fromFile產出一個迭代器。另外Iterable中的一些方法(grouped或sliding)產生迭代器。
流(stream)提供的是一個不可變的替代品,是一個尾部被懶計算的不可變列表。
def numsFrom(n: BigInt): Stream[BigInt] = n #:: numsFrom(n + 1)// 通過#::構建流val tenOrMore = numsFrom(10)// 返回 Stream(10, ?)tenOrMore.tail.tail.tail// 返回 Stream(13, ?)// 強制求所有值tenOrMore.take(5).force// 可以從迭代器構造一個流val Words = Source.fromFile("/usr/share/dict/words").getLines.toStreamwords // Stream(A, ?)words(5) // Aachenwords // Stream(A, A's, AOL, AOL's, Aachen, ?)// 迭代器對于每行只能訪問一次,而流將緩存訪問過的行,允許重新訪問使用view方法,同樣可以獲得一個被懶執行的集合。
import scala.math._val powers = (0 until 1000).view.map(pow(10, _))// powers: scala.collection.SeqView[Double,Seq[_]] = SeqViewM(...)powers(2)// res12: Double = 100.0// 也可以用force方法進行強制求值。從Scala集合到Java集合的轉換
隱式函數 | scala.collection類型 | java.util類型 |
---|---|---|
asJavaCollection | Iterable | Collection |
asJavaIterable | Iterable | Iterable |
asJavaIterator | Iterator | Iterator |
asJavaEnumeration | Iterator | Enumeration |
seqAsJavaList | Seq | List |
mutableSeqAsjavaList | mutable.Seq | List |
bufferAsJavaList | mutable.Buffer | List |
setAsJavaSet | Set | Set |
mutableSetAsJavaSet | mutable.Set | Set |
mapAsJavaMap | Map | Map |
mutableMapAsJavaMap | mutable.Map | Map |
asJavaDictionary | Map | Dictionary |
asJavaConcurrentMap | mutable.ConcurrentMap | concurrent.ConcurrentMap |
從Java集合到Scala集合的轉換
隱式函數 | java.util類型 | scala.collection類型 |
---|---|---|
collectionAsScalaIterable | Collection | Iterable |
IterableAsScalaIterable | Iterable | Iterable |
asScalaIterator | Iterator | Iterator |
enumerationAsScalaIterator | Enumeration | Iterator |
asScalaBuffer | List | mutable.Buffer |
asScalaSet | Set | mutable.Set |
mapAsScalaMap | Map | mutable.Map |
dictionaryAsScalaMap | Dictionary | mutable.Map |
propertiesAsScalaMap | Properties | mutable.Map |
asScalaConcurentMap | concurrent.ConcurrentMap | mutable.ConcurrentMap |
Scala類庫提供了六個特質,可以將它們混入集合,讓集合的操作變成同步的:
SynchronizedBufferSynchronizedMapSynchronizedPriorityQueueSynchronizedQueueSynchronizedSetSynchronizedStack示例
val scores = new scala.collection.mutable.HashMap[String, Int] with scala.collection.mutable.SynchronizedMap[String, Int]par方法產出集合的一個并行實現。
// 通過par并行化for循環,結果是按線程產出的順序輸出的scala> for (i <- (0 until 100).par) print(i + " ")50 25 0 75 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 26 27 28 29 30 31 32 33 34 35 36 37 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 38 39 40 41 42 43 44 45 46 47 48 49 87 88 89 90 91 92 93 94 95 96 97 98 99 81 82 83 84 85 86 78 79 80 76 77// 在for/yield循環中,結果是依次組裝的scala> for (i <- (0 until 100).par) yield i + " "res15: scala.collection.parallel.immutable.ParSeq[String] = ParVector(0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 , 24 , 25 , 26 , 27 , 28 , 29 , 30 , 31 , 32 , 33 , 34 , 35 , 36 , 37 , 38 , 39 , 40 , 41 , 42 , 43 , 44 , 45 , 46 , 47 , 48 , 49 , 50 , 51 , 52 , 53 , 54 , 55 , 56 , 57 , 58 , 59 , 60 , 61 , 62 , 63 , 64 , 65 , 66 , 67 , 68 , 69 , 70 , 71 , 72 , 73 , 74 , 75 , 76 , 77 , 78 , 79 , 80 , 81 , 82 , 83 , 84 , 85 , 86 , 87 , 88 , 89 , 90 , 91 , 92 , 93 , 94 , 95 , 96 , 97 , 98 , 99 )par方法返回的并行集合的類型為擴展自ParSeq、ParSet或ParMap特質的類型,所有這些特質都是ParIterable的子類型。但這些并不是Iterable的子類型,因此不能講并行集合傳遞給預期Iterable、Seq、Set或Map的方法。
1. 編寫一個函數,給定字符串,產出一個包含所有字符的下標的映射。舉例來說:indexes(“Mississippi”)應返回一個映射,讓’M’對應集{0},’i’對應集{1,4,7,10},依此類推。使用字符到可變集的映射。另外,你如何保證集是經過排序的?
import scala.collection.mutableobject One { def indexes(str: String): mutable.HashMap[Char, mutable.SortedSet[Int]] = { val map = new mutable.HashMap[Char, mutable.SortedSet[Int]] var i = 0 str.foreach { c => map.get(c) match { case Some(value) => map(c) = value + i case None => map += (c -> mutable.SortedSet { i }) } i += 1 } map } def main(args: Array[String]) { println(indexes("Mississippi")) }}// 結果Map(M -> TreeSet(0), s -> TreeSet(2, 3, 5, 6), p -> TreeSet(8, 9), i -> TreeSet(1, 4, 7, 10))2. 重復前一個練習,這次用字符到列表的不可變映射。
import scala.collection.{immutable, mutable}object Two { def indexes(str: String): mutable.HashMap[Char, immutable.ListSet[Int]] = { val map = new mutable.HashMap[Char, immutable.ListSet[Int]] var i = 0 str.foreach { c => map.get(c) match { case Some(value) => map(c) = value + i case None => map += (c -> immutable.ListSet { i }) } i += 1 } map } def main(args: Array[String]) { println(indexes("Mississippi")) }}// 結果Map(M -> ListSet(0), s -> ListSet(6, 5, 3, 2), p -> ListSet(9, 8), i -> ListSet(10, 7, 4, 1))3. 編寫一個函數,從一個整型鏈表中去除所有的零值。
object Three { def removeZero(list: List[Int]): List[Int] = { list.filter(_ != 0) } def main(args: Array[String]) { println(removeZero(List(3, 25, 0, 2, 0, 0))) }}4. 編寫一個函數,接受一個字符串的集合,以及一個從字符串到整數值的映射。返回整型的集合,其值為能和集合中某個字符串相對應的映射的值。舉例來說,給定Array(“Tom”,”Fred”,”Harry”)和Map(“Tom”->3,”Dick”->4,”Harry”->5),返回Array(3,5)。提示:用flatMap將get返回的Option值組合在一起
object Four { def filterMap(arr: Array[String], map: Map[String, Int]) = { // map.get返回Some(v), 才會被返回 arr.flatMap(map.get) } def main(args: Array[String]) { println(filterMap(Array("Tom", "Fred", "Harry"), Map("Tom" -> 3, "Dick" -> 4, "Harry" -> 5)).mkString(",")) }}5. 實現一個函數,作用與mkString相同,使用reduceLeft。
import scala.collection.mutabletrait MktString { this: mutable.Iterable[String] => def mktString(split: String = "") = if (this != Nil) this.reduceLeft(_ + split + _)}object Five { def main(args: Array[String]) { var test = new scala.collection.mutable.HashSet[String] with MktString test += "1" test += "2" test += "3" println(test.mktString(",")) }}// 結果3,1,26. 給定整型列表lst,(lst :/ List[Int]())(_ :: _)得到什么?(List[Int]() /: lst)(_ :+ _)又得到什么?如何修改它們中的一個,以對原列表進行反向排序?
object Six { def main(args: Array[String]) { val lst = List(1, 2, 3, 4, 5) // foldRight方法, List[Int]()是初始值(為空)被增加到右邊 // :: 與 +:相同, 頭部添加 println((lst :/ List[Int]()) (_ :: _)) // foldLeft方法, List[Int]()是初始值(為空)被增加到左邊 // :+ 尾部添加 println((List[Int]() /: lst) (_ :+ _)) println((List[Int]() /: lst) ((a, b) => b :: a)) }}// 結果List(1, 2, 3, 4, 5)List(1, 2, 3, 4, 5)List(5, 4, 3, 2, 1)7. 在13.11節中,表達式(prices zip quantities) map { p => p.1 * p.2}有些不夠優雅。我們不能用(prices zip quantities) map { * _ },因為 _ * _ 是一個帶兩個參數的函數,而我們需要的是一個帶單個類型為元組的參數的函數,Function對象的tupled方法可以將帶兩個參數的函數改為以元組為參數的函數。將tupled應用于乘法函數,以使我們可以用它來映射由對偶組成的列表。
object Seven { def main(args: Array[String]) { val prices = List(5.0, 20.0, 9.95) val quantities = List(10, 2, 1) println((prices zip quantities) map { Function.tupled(_ * _) }) }}8. 編寫一個函數。將Double數組轉換成二維數組。傳入列數作為參數。舉例來說,Array(1,2,3,4,5,6)和三列,返回Array(Array(1,2,3),Array(4,5,6))。用grouped方法。
object Eight { def toMultiDim(arr: Array[Double], n: Int): Array[Array[Double]] = { // grouped產出下標為[0, n)的元素,然后是[n, 2*n)的元素 arr.grouped(n).toArray } def main(args: Array[String]) { val arr = Array(1.0, 2, 3, 4, 5, 6) toMultiDim(arr, 3).foreach(a => println(a.mkString(","))) }}9. Harry Hacker寫了一個從命令行接受一系列文件名的程序。對每個文件名,他都啟動一個新的線程來讀取文件內容并更新一個字母出現頻率映射,聲明為:
val frequencies = new scala.collection.multable.HashMap[Char,Int] with scala.collection.mutable.SynchronizedMap[Char,Int]當讀到字母c時,他調用
frequencies(c) = frequencies.getOrElse(c,0) + 1為什么這樣做得不到正確答案?如果他用如下方式實現呢:
import scala.collection.JavaConversions.asScalaConcurrentMapval frequencies:scala.collection.mutable.ConcurrentMap[Char,Int] = new java.util.concurrent.ConcurrentHashMap[Char,Int]并發修改集合不安全。10. Harry Hacker把文件讀取到字符串中,然后想對字符串的不同部分用并行集合來并發地更新字母出現頻率映射。他用了如下代碼:
val frequencies = new scala.collection.mutable.HashMap[Char,Int]for(c <- str.par) frequencies(c) = frequencies.getOrElse(c,0) + 1為什么說這個想法很糟糕?要真正地并行化這個計算,他應該怎么做呢?(提示:用aggregate)
并行修改共享變量,結果無法估計。
object Ten { def main(args: Array[String]) { val str = "abdcsdcd" // aggregate將操作符應用于集合的不同部分 val frequencies = str.par.aggregate(HashMap[Char, Int]())( { (a, b) => a + (b -> (a.getOrElse(b, 0) + 1)) } , { (map1, map2) => (map1.keySet ++ map2.keySet).foldLeft(HashMap[Char, Int]()) { (result, k) => result + (k -> (map1.getOrElse(k, 0) + map2.getOrElse(k, 0))) } } ).foreach(println) }}// 結果(s,1)(a,1)(b,1)(c,2)(d,3)基本用法
str(i) match { case '+' => sign = 1 case '-' => sign = -1 // 可以增加守衛,同樣也可以用變量 case ch if Character.isDigit(ch) => digit = Character.digit(ch, 10) case _ => sign = 0 // 與switch的default等效}// 如果沒有模式能匹配,代碼會拋出MatchError// match也是表達式,而不是語句sign = ch match { case '+' => 1 case '-' => -1 case _ => 0}變量模式可能會與常量表達式沖突,例如:
import scala.math._x match { case Pi => ... // Pi是常量,變量必須以小寫字母開頭 case `test` => ... // 如果有一個小寫字母開頭的常量,需要將它包仔反引號中 ...}類型模式
obj match { case x: Int => x case s: String => Integer.parseInt(s) case m: Map[String, Int] => ... // 由于泛型在Java虛擬機中會被擦掉,所以別這樣做 case m: Map[_, _] => ... // OK case _: BigInt => Int.maxValue case _ => 0}匹配數組、列表或元組
數組
arr match { case Array(0) => "0" // 匹配包含0的數組 case Array(x, y) => x + " " + y // 匹配任何帶有兩個元素的數組,并綁定變量 case Array(0, _*) => "0 ..." // 匹配任何以零開始的數組 case _ => "something else"}列表
lst match { case 0 :: Nil => "0" case x :: y :: Nil => x + " " + y case 0 :: tail => "0 ..." case _ => "something else"}元組
pair match { case (0, _) => "0 ..." case (y, 0) => y + " 0" case _ => "neither is 0"}提取器
模式是如何匹配數組、列表和元組的,背后是提取器(extractor)機制,即帶有從對象中提取值的unapply或unapplySeq方法的對象。
正則表達式是另一個適合使用提取器的場景。
val pattern = "([0-9]+) ([a-z]+)".r"99 bottles" match { case pattern(num, item) => ...}變量聲明中的模式
scala> val (x, y) = (1, 2)x: Int = 1y: Int = 2scala> val (q, r) = BigInt(10) /% 3q: scala.math.BigInt = 3r: scala.math.BigInt = 1scala> val Array(first, second, _*) = Array(1, 2, 3, 4, 5)first: Int = 1second: Int = 2for表達式中的模式
import scala.collection.JavaConversions.propertiesAsScalaMapfor ((k, v) <- System.getProperties()) println(k + " -> " + v)// 打印值為空白的鍵for ((k, "") <- System.getProperties()) println(k)// 同樣可以使用守衛for ((k, v) <- System.getProperties() if v == "") println(k)樣例類是一種特殊的類,經過優化已被用于模式匹配。
基本用法
// 擴展自常規類的樣例類abstract class Amountcase class Dollar(value: Double) extends Amountcase class Currency(value: Double, unit: String) extends Amount// 針對單例的樣例對象case object Nothing extends Amount// 示例amt match { case Dollar(v) => "$" + v case Currency(_, u) => "Oh noes, I got " + u case Nothing => ""}當聲明樣例類時,如下幾件事自動發生:
構造器中的每一個參數都成為val--除非它唄顯示地聲明為var。在伴生對象中提供apply方法以便不用new關鍵字創建對象。提供unapply方法讓模式匹配可以工作將生成toString、equals、hashCode和copy方法copy方法和帶名參數
val amt = Currency(29.94, "EUR")// copy方法創建一個與現有對象相同的新對象val price = amt.copy()// 也可以用帶名參數來修改某些屬性val price = amt.copy(value = 19.21)case語句中的中值表示法
如果unapply方法產出一個對偶,則可以在case語句中使用中置表示法。
case class ::[E](head: E, tail: List[E]) extends List[E]lst match { case h :: t => ... }// 等同于case ::(h, t),將調用::.unapply(lst)匹配嵌套結構
例如,某個商店售賣的物品。有時,我們會將物品捆綁在一起打折銷售
abstract class Itemcase class Article(description: String, price: Double) extends Itencase class Bundle(description: String, discount: Double, item: Item*) extends Item// 使用Bundle("Father's day special", 20.0, Article("Scala for the Impatient", 39.95), Bundle("Anchor Distillery Sampler", 10.0) Article("Old potrero Straight Rye Whisky", 69.21), Article("Junipero Gin", 79.95),// 模式可以匹配到特定的嵌套case Bundle(_, _, Article(descr, _), _*) => ...// 可以使用@表示法將嵌套的值綁到變量// 以下是一個計算某Item價格的函數def price(it: Item): Double = it match { case Article(_, p) => p case Bundle(_, disc, its @ _*) => its.map(price _).sum - disc}密封類
超類聲明為sealed,就是密封類,其子類都必須在與該密封類相同的文件中定義。而且在編譯期所有子類都是可知的,因而編譯器可以檢查模式語句的完整性。
sealed abstract class Amountcase class Dollar(value: Double) extends Amountcase class Currency(value: Double, unit: String) extends Amount模擬枚舉
sealed abstract class TrafficLightColorcase object Red extends TrafficLightColorcase object Yellow extends TrafficLightColorcase object Green extends TrafficLightColorcolor match { case Red => "stop" case Yellow => "hurry up" case Green => "go"}標準類庫中的Option類型用樣例類來表示那種可能存在、也可能不存在的值。
樣例子類Some包裝了某個值,例如:Some(“Fred”)。而樣例對象None表示沒有值。
Option支持泛型。例如Some(“Fred”)的類型為Option[String]。
被包在花括號內的一組case語句是一個便函數,即一個并非對所有輸入值都有定義的函數。它是PartialFunction[A, B]類的一個實例。A是參數類型,B是返回類型。該類有兩個方法:apply方法從匹配到的模式計算函數值,而isDefinedAt方法在輸入至少匹配其中一個模式時返回true。
val f: PartialFunction[Char, Int] = { case '+' => 1; case '-' => -1 }f('-') // 調用f.apply('-'),返回-1f.isDefinedAt('0') // falsef('0') // 拋出MatchError1. JDK發行包有一個src.zip文件包含了JDK的大多數源代碼。解壓并搜索樣例標簽(用正則表達式case [^:]+:)。然后查找以//開頭并包含[Ff]alls?thr的注釋,捕獲類似// Falls through或// just fall thru這樣的注釋。假定JDK的程序員們遵守Java編碼習慣,在該寫注釋的地方寫下了這些注釋,有多少百分比的樣例是會掉入到下一個分支的?
略
2. 利用模式匹配,編寫一個swap函數,接受一個整數的對偶,返回對偶的兩個組成部件互換位置的新對偶
object Two extends App { def swap[S, T](tup: (S, T)) = { tup match { case (a, b) => (b, a) } } println(swap[String, Int](("1", 2)))}3. 利用模式匹配,編寫一個swap函數,交換數組中的前兩個元素的位置,前提條件是數組長度至少為2
object Three extends App { def swap(arr: Array[Any]) = { arr match { case Array(first, second, rest@_*) => Array(second, first) ++ rest case _ => arr } } println(swap(Array("1", "2", "3", "4")).mkString(","))}// 結果2,1,3,44. 添加一個樣例類Multiple,作為Item的子類。舉例來說,Multiple(10,Article(“Blackwell Toster”,29.95))描述的是10個烤面包機。當然了,你應該可以在第二個參數的位置接受任何Item,無論是Bundle還是另一個Multiple。擴展price函數以應對新的樣例。
object Four extends App { abstract class Item case class Multiple(num: Int, item: Item) extends Item case class Article(description: String, price: Double) extends Item case class Bundle(description: String, discount: Double, item: Item*) extends Item def price(it: Item): Double = it match { case Article(_, a) => a case Bundle(_, disc, its@_*) => its.map(price).sum - disc case Multiple(n, t) => n * price(t) } val p = price(Multiple(10, Article("Blackwell Toster", 29.95))) println(p)}// 結果299.55. 我們可以用列表制作只在葉子節點存放值的樹。舉例來說,列表((3 8) 2 (5))描述的是如下這樣一棵樹:
. / | / . 2 . / / |3 8 5不過,有些列表元素是數字,而另一些是列表。在Scala中,你不能擁有異構的列表,因此你必須使用List[Any]。編寫一個leafSum函數,計算所有葉子節點中的元素之和,用模式匹配來區分數字和列表。
object Five extends App { def leafSum(list: List[Any]): Int = { var total = 0 list.foreach { case l: List[Any] => total += leafSum(l) case i: Int => total += i } total } val l: List[Any] = List(List(3, 8), 2, List(5)) println(leafSum(l))}// 結果186. 制作這樣的樹更好的做法是使用樣例類。我們不妨從二叉樹開始。
sealed abstract class BinaryTreecase class Leaf(value : Int) extends BinaryTreecase class Node(left : BinaryTree,right : BinaryTree) extends BinaryTree編寫一個函數計算所有葉子節點中的元素之和。
object Six extends App { sealed abstract class BinaryTree case class Leaf(value: Int) extends BinaryTree case class Node(left: BinaryTree, right: BinaryTree) extends BinaryTree def leafSum(tree: BinaryTree): Int = { tree match { case Node(a, b) => leafSum(a) + leafSum(b) case Leaf(v) => v } } val r = Node(Leaf(3), Node(Leaf(3), Leaf(9))) println(leafSum(r))}// 結果157. 擴展前一個練習中的樹,使得每個節點可以有任意多的后代,并重新實現leafSum函數。第五題中的樹應該能夠通過下述代碼表示:
Node(Node(Leaf(3),Leaf(8)),Leaf(2),Node(Leaf(5)))object Seven extends App { sealed abstract class BinaryTree case class Leaf(value: Int) extends BinaryTree case class Node(tr: BinaryTree*) extends BinaryTree def leafSum(tree: BinaryTree): Int = { tree match { case Node(r @ _*) => r.map(leafSum).sum case Leaf(v) => v } } val r = Node(Node(Leaf(3), Leaf(8)), Leaf(2), Node(Leaf(5))) println(leafSum(r))}// 結果188. 擴展前一個練習中的樹,使得每個非葉子節點除了后代之外,能夠存放一個操作符。然后編寫一個eval函數來計算它的值。舉例來說:
+ / | / * 2 - / / |3 8 5上面這棵樹的值為(3 * 8) + 2 + (-5) = 21
object Eight extends App { sealed abstract class BinaryTree case class Leaf(value: Int) extends BinaryTree case class Node(op: Char, leafs: BinaryTree*) extends BinaryTree def eval(tree: BinaryTree): Int = { tree match { case Node(op, leafs@_*) => op match { case '+' => leafs.map(eval).sum case '-' => -leafs.map(eval).sum case '*' => leafs.map(eval).product } case Leaf(v) => v } } val x = Node('+', Node('*', Leaf(3), Leaf(8)), Leaf(2), Node('-', Leaf(5))) println(x) println(eval(x))}// 結果Node(+,WrappedArray(Node(*,WrappedArray(Leaf(3), Leaf(8))), Leaf(2), Node(-,WrappedArray(Leaf(5)))))219. 編寫一個函數,計算List[Option[Int]]中所有非None值之和。不得使用match語句。
object Nine extends App { def sum(lst: List[Option[Int]]) = lst.map(_.getOrElse(0)).sum val x = List(Some(1), None, Some(2), None, Some(3)) println(sum(x))}10. 編寫一個函數,將兩個類型為Double=>Option[Double]的函數組合在一起,產生另一個同樣類型的函數。如果其中一個函數返回None,則組合函數也應返回None。例如:
def f(x : Double) = if ( x >= 0) Some(sqrt(x)) else Nonedef g(x : Double) = if ( x != 1) Some( 1 / ( x - 1)) else Noneval h = compose(f,g)h(2)將得到Some(1),而h(1)和h(0)將得到None
object Ten extends App { def compose(f: Double => Option[Double], g: Double => Option[Double]) = { (x: Double) => if (f(x).isEmpty || g(x).isEmpty) None else g(x) } import scala.math.sqrt def f(x: Double) = if (x >= 0) Some(sqrt(x)) else None def g(x: Double) = if (x != 1) Some(1 / (x - 1)) else None val h = compose(f, g) println(h(0)) println(h(1)) println(h(2))}// 結果Some(-1.0)NoneSome(1.0)習題解答
新聞熱點
疑難解答