fold/foldLeft/foldRight區別和聯繫

1. fold介紹

從本質上說,fold函數將一種格式的輸入數據轉化成另外一種格式返回fold, foldLeftfoldRight這三個函數除了有一點點不同外,做的事情差不多。我將在下文解釋它們的共同點並解釋它們的不同點。

我將從一個簡單的例子開始,用fold計算一系列整型的和。

val numbers = List(5, 4, 8, 6, 2)
numbers.fold(0) { (z, i) =>
  z + i
}
// result = 25

List中的fold方法需要輸入兩個參數:初始值以及一個函數。輸入的函數也需要輸入兩個參數:累加值當前item的索引。那麼上面的代碼片段發生了什麼事?

  1. 代碼開始運行的時候,初始值0作爲第一個參數傳進到fold函數中,list中的第一個item作爲第二個參數傳進fold函數中。
  2. fold函數開始對傳進的兩個參數進行計算,在本例中,僅僅是做加法計算,然後返回計算的值;
  3. Fold函數然後將上一步返回的值作爲輸入函數的第一個參數,並且把list中的下一個item作爲第二個參數傳進繼續計算,同樣返回計算的值;
  4. 第2步將重複計算,直到list中的所有元素都被遍歷之後,返回最後的計算值,整個過程結束;
  5. 這雖然是一個簡單的例子,讓我們來看看一些比較有用的東西。早在後面將會介紹foldleft函數,並解釋它和fold之間的區別,目前,你只需要想象foldLeft函數和fold函數運行過程一樣。

2. foldLeft介紹

下面是一個簡單的類和伴生類:

class Foo(val name: string, val age: int, val sex: symbol)
object Foo {
  def apply(name: string, age: int, sex: symbol) = new foo(name, age, sex)
}

假如我們有很多的Foo實例,並存在list中:

val fooList = foo("hugh jass", 25, 'male) ::
              foo("biggus dickus", 43, 'male) ::
              foo("incontinentia buttocks", 37, 'female) ::
              nil

我們想將上面的list轉換成一個存儲[title] [name], [age]格式的string鏈表:

val stringlist = foolist.foldleft(list[string]()) { (z, f) =>
  val title = f.sex match {
    case 'male => "mr."
    case 'female => "ms."
  }
  z :+ s"$title ${f.name}, ${f.age}"
}

// stringList(0)
// Mr. Hugh jass, 25
// stringList(2)
// Ms. Incontinentia buttocks, 37

  
和第一個例子一樣,我們也有個初始值,這裏是一個空的string list,也有一個操作函數。在本例中,我們判斷了性別,並構造了我們想要的string,並追加到累加器中(這裏是一個list)。


3. fold, foldleft, and foldright之間的區別

主要的區別是操作函數遍歷問題集合的順序。foldleft是從左開始計算,然後往右遍歷。foldright是從右開始算,然後往左遍歷。而fold遍歷的順序沒有特殊的次序。來看下這三個函數的實現吧(在traversableonce特質裏面實現)

def fold[A1 >: A](z: A1)(op: (A1, A1) => A1): A1 = foldLeft(z)(op)

def foldLeft[B](z: B)(op: (B, A) => B): B = {
  var result = z
  this.seq foreach (x => result = op(result, x))
  result
}

def foldRight[B](z: B)(op: (A, B) => B): B =
  reversed.foldLeft(z)((x, y) => op(y, x))

由於fold函數遍歷沒有特殊的次序,所以對fold的初始化參數和返回值都有限制。在這三個函數中,初始化參數和返回值的參數類型必須相同。

第一個限制是初始值的類型必須是list中元素類型的超類。在我們的例子中,我們的對List[Int]進行fold計算,而初始值是Int類型的,它是List[Int]的超類。

第二個限制是初始值必須是中立的(neutral)。也就是它不能改變結果。比如對加法來說,中立的值是0;而對於乘法來說則是1,對於list來說則是Nil。

順便說下,其實foldLeft和foldRight函數還有兩個縮寫的函數:

def /:[B](z: B)(op: (B, A) => B): B = foldLeft(z)(op)

def :\[B](z: B)(op: (A, B) => B): B = foldRight(z)(op)

scala> (0/:(1 to 100))(_+_) 
res32: Int = 5050

scala> ((1 to 100):\0)(_+_)
res24: Int = 5050

【完】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章