第十章 Scala 容器基礎(二十):使用reduce和fold方法遍歷集合的全部元素

Problem

    你想要遍歷有序集合的全部元素,而且隨着你對集合元素的遍歷,對比兩個相鄰的元素es6

Solution

    使用reduceLeft, foldLeft, reduceRight, foldRight來遍歷集合的元素,你的方法做用在相鄰的兩個元素上,從第一次要遍歷的兩個相鄰元素開始,把你的方法做用在這兩個元素上獲得返回值,而後把你的方法繼續做用在返回值和集合中第三要遍歷的元素獲得的返回值,再繼續和第四個要遍歷的元素做用。。。直到遍歷完最後一個元素爲止:算法

scala> val a = Array(12, 6, 15, 2, 20, 9)
a: Array[Int] = Array(12, 6, 15, 2, 20, 9)

scala> a.reduceLeft(_ + _)
res32: Int = 64

    這個例子是這樣的:12+6=18, 18+15=33, 33+2=35, 35+20=55, 55+9=64,就是對集合的全部元素求和
ide

    接下來你會看到如何使用reduceLeft來計算集合元素的乘積和求最大最小值:函數

scala> a.reduceLeft(_ * _)
res33: Int = 388800

scala> a.reduceLeft(_ min _)
res34: Int = 2

scala> a.reduceLeft(_ max _)
res35: Int = 20
Show each step in the process

    咱們來看看reduceLeft的執行細節:es5

val findMax = (x: Int, y: Int) => {
  val winner = x max y
  println(s"compared $x to $y, $winner was larger")
  winner
}
scala> a.reduceLeft((x,y) => findMax(x,y))
compared 12 to 6, 12 was larger
compared 12 to 15, 15 was larger
compared 15 to 2, 15 was larger
compared 15 to 20, 20 was larger
compared 20 to 9, 20 was larger
res38: Int = 20

    上面的輸出信息展現了reduceLeft是如何遍歷集合中的每個元素,並在每一步時是如何調用傳入的函數操做集合元素的。咱們來總結一下reduceLeft的操做步驟:
spa

  • reduceLeft開始調用findMax方來來比較集合的前兩個元素,12和6,findMax方法返回12,由於12>6scala

  • reduceLeft接下來使用第一次調用findMax的返回值12和集合的第三個元素15,調用findMax(12, 15),由於15>12因此findMax返回15debug

  • reduceLeft接下來用每一步執行的返回值和集合的下一個元素傳入findMax方法,返回較大的值,直到遍歷完集合的最後一個元素,返回最大值20code

    咱們來換種方法來模擬reduceLeft的功能:ip

// you provide the sequence 'seq' and the function 'f'
var result = seq(0)
for (i <- 1 until seq.length) {
    val next = seq(i)
    result = f(result, next)
}

    一個關於reduceLeft方法很是微妙而且重要的事項:傳入的方法的返回值類型必須是要和集合中存儲的數據類型相同的。這是很是必要的,由於reduceLeft會對比方法的返回值和集合中的下一個元素。

Working with other sequences and types

    正如你想象的,集合包涵的數據類型能夠是任何你想要的。舉個例子,遍歷一個字符串序列經過一個方法來肯定最長的活着最短的字符串:

scala> val peeps = Vector("al", "hannah", "emily", "christina", "aleka")
peeps: scala.collection.immutable.Vector[String] = Vector(al, hannah, emily, christina, aleka)

scala> peeps.reduceLeft((x,y) => if(x.length >= y.length) x else y)
res5: String = christina
foldLeft, reduceRight, and foldRight

    方法foldLeft與reduceLeft工做方法很想,可是它讓你指定一個值做爲第一個元素。下面這個例子真是了求和算法,第一個是reduceLeft方法,後面是foldLeft方法,咱們來看看它們的區別:

scala> val a = Array(1, 2, 3)
a: Array[Int] = Array(1, 2, 3)

scala> a.reduceLeft(_+_)
res6: Int = 6

scala> a.foldLeft(100)(_+_)
res7: Int = 106

scala> a.foldLeft(200)(_+_)
res8: Int = 206

    上面最後兩個例子中foldLeft分別使用了100,200做爲首元素,這個值直接影響到了最終求和的結果。若是你還從沒有見過這樣的語法,就拿foldLeft接受兩個參數列表來解釋一下把。第一個參數表接受一個字段,種子值。第二個參數表是一個你想要運行的代碼塊。方法reduceRight和foldRight執行方式和reduceLeft和foldLeft同樣,可是它們是從集合的最後一個元素開始遍歷,而後從右到左,直到遍歷到集合的開始位置。

The difference between reduceLeft and reduceRight

    在許多算法中,你可能並不在乎使用reduceLeft仍是reduceRight。在這種狀況下,你可使用reduce方法代替。Scala文檔中對reduce的說明:「對元素的操做順序是不明的或者不肯定的」。

    可是還有許多算法會對兩種方法返回不一樣的結果。舉個例子,divide函數:

val divide = (x: Double, y: Double) => {
  val result = x / y
  println(s"divided $x by $y to yield $result")
  result
}

scala> def divide(x:Double, y:Double):Double = {
     |   val result = x / y
     |   println(s"divided $x by $y to yield $result")
     |   result
     | }
divide: (x: Double, y: Double)Double

    定義一個集合,分別查看調用reduceLeft和reduceRight的結果:

scala> val a = Array(1.0, 2.0, 3.0)

scala> a.reduceLeft((x,y) => divide(x,y))
divided 1.0 by 2.0 to yield 0.5
divided 0.5 by 3.0 to yield 0.16666666666666666
res10: Double = 0.16666666666666666

scala> a.reduceRight((x,y) => divide(x,y))
divided 2.0 by 3.0 to yield 0.6666666666666666
divided 1.0 by 0.6666666666666666 to yield 1.5
res11: Double = 1.5
scanLeft and scanRight

    方法scanLeft和scanRight也會變了整個集合,相似於reduceLeft和reduceRight,可是它們返回一個集合而不是一個值。

    舉個例子,scanLeft產生一個集合,集合元素爲從左到右遍歷遍歷集合並對集合元素調用操做函數產生返回值。爲了理解這事如何工做的,咱們新建一個帶有debug的方法:

scala> def product(x:Int, y:Int):Int = {
     |   val result = x * y
     |   println(s"multiplied $x by $y to yield $result")
     |   result
     | }
product: (x: Int, y: Int)Int

scala> val a = Array(1,2,3)
a: Array[Int] = Array(1, 2, 3)

scala> a.scanLeft(10)(product)
multiplied 10 by 1 to yield 10
multiplied 10 by 2 to yield 20
multiplied 20 by 3 to yield 60
res12: Array[Int] = Array(10, 10, 20, 60)

    正如你看到的,scanLeft返回了一個新的集合,而不是一個值。方法scanRight以一樣的方式工做,不過是從右到左遍歷集合。

    那還有一些相關的方法,包含reduce,reduceLeftOption,reduceRightOption。

    若是你關心reduce方法的狀況,那麼運行這段代碼來看看吧:

scala> def findMax(x:Int, y:Int):Int = {
     |   Thread.sleep(10)
     |   val winner = x max y
     |   println(s"compared $x and $y, the winner is $winner")
     |   winner
     | }
findMax: (x: Int, y: Int)Int

scala> val a = Array.range(0,50)
a: Array[Int] = Array(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)

scala> a.reduce(findMax)
compared 0 and 1, the winner is 1
compared 1 and 2, the winner is 2
compared 2 and 3, the winner is 3
compared 3 and 4, the winner is 4
compared 4 and 5, the winner is 5
compared 5 and 6, the winner is 6
compared 6 and 7, the winner is 7
compared 7 and 8, the winner is 8
compared 8 and 9, the winner is 9
compared 9 and 10, the winner is 10
compared 10 and 11, the winner is 11
compared 11 and 12, the winner is 12
compared 12 and 13, the winner is 13
compared 13 and 14, the winner is 14
compared 14 and 15, the winner is 15
compared 15 and 16, the winner is 16
compared 16 and 17, the winner is 17
compared 17 and 18, the winner is 18
compared 18 and 19, the winner is 19
compared 19 and 20, the winner is 20
compared 20 and 21, the winner is 21
compared 21 and 22, the winner is 22
compared 22 and 23, the winner is 23
compared 23 and 24, the winner is 24
compared 24 and 25, the winner is 25
compared 25 and 26, the winner is 26
compared 26 and 27, the winner is 27
compared 27 and 28, the winner is 28
compared 28 and 29, the winner is 29
compared 29 and 30, the winner is 30
compared 30 and 31, the winner is 31
compared 31 and 32, the winner is 32
compared 32 and 33, the winner is 33
compared 33 and 34, the winner is 34
compared 34 and 35, the winner is 35
compared 35 and 36, the winner is 36
compared 36 and 37, the winner is 37
compared 37 and 38, the winner is 38
compared 38 and 39, the winner is 39
compared 39 and 40, the winner is 40
compared 40 and 41, the winner is 41
compared 41 and 42, the winner is 42
compared 42 and 43, the winner is 43
compared 43 and 44, the winner is 44
compared 44 and 45, the winner is 45
compared 45 and 46, the winner is 46
compared 46 and 47, the winner is 47
compared 47 and 48, the winner is 48
compared 48 and 49, the winner is 49
res13: Int = 49
相關文章
相關標籤/搜索