foldLeft
とreduceLeft
の基本的な違いを学びました
foldLeft:
reduceLeft:
他に違いはありますか?
同様の機能を持つ2つのメソッドがある特定の理由はありますか?
実際の答えを出す前に、ここで言及することはほとんどありません。
left
とは何の関係もありません。それはむしろ、縮小と折りたたみの違いに関するものです。質問に戻る:
foldLeft
の署名は次のとおりです(これから説明するポイントでは、foldRight
である場合もあります)。
def foldLeft [B] (z: B)(f: (B, A) => B): B
そして、これはreduceLeft
の署名です(ここでも方向は関係ありません)
def reduceLeft [B >: A] (f: (B, A) => B): B
これら2つは非常によく似ているため、混乱を引き起こしました。 reduceLeft
は、foldLeft
の特殊なケースです(ちなみに、sometimesは、どちらかを使用して同じことを表現できることを意味します)。
reduceLeft
をList[Int]
で呼び出すと、整数のリスト全体が文字列で単一の値に縮小され、Int
型(またはInt
のスーパータイプ、したがって[B >: A]
)になります。
foldLeft
をList[Int]
で呼び出すと、リスト全体が折り返されて(1枚の紙を転がすことを想像して)単一の値になりますが、この値はInt
に関連する必要さえありません(したがって[B]
)。
以下に例を示します。
def listWithSum(numbers: List[Int]) = numbers.foldLeft((List[Int](), 0)) {
(resultingTuple, currentInteger) =>
(currentInteger :: resultingTuple._1, currentInteger + resultingTuple._2)
}
このメソッドはList[Int]
を取り、Tuple2[List[Int], Int]
または(List[Int] -> Int)
を返します。合計を計算し、整数のリストとその合計を含むタプルを返します。ところで、foldLeft
の代わりにfoldRight
を使用したため、リストは逆方向に返されます。
より詳細な説明については、 すべてを支配するために1つ折り を参照してください。
reduceLeft
は単なる便利なメソッドです。それは同等です
list.tail.foldLeft(list.head)(_)
foldLeft
はより汎用的であり、元のデータとはまったく異なるものを生成するために使用できます。これに対して、reduceLeft
はコレクション型と同じ型またはスーパー型の最終結果しか生成できません。例えば:
List(1,3,5).foldLeft(0) { _ + _ }
List(1,3,5).foldLeft(List[String]()) { (a, b) => b.toString :: a }
foldLeft
は、最後に折り畳まれた結果(最初に初期値を使用)と次の値でクロージャーを適用します。
一方、reduceLeft
は最初にリストの2つの値を結合し、クロージャーに適用します。次に、残りの値と累積結果を組み合わせます。見る:
List(1,3,5).reduceLeft { (a, b) => println("a " + a + ", b " + b); a + b }
リストが空の場合、foldLeft
は初期値を正当な結果として提示できます。一方、reduceLeft
は、リスト内に少なくとも1つの値が見つからない場合、正当な値を持ちません。
参考のために、空のコンテナにreduceLeft
を適用すると、次のエラーが発生してエラーになります。
Java.lang.UnsupportedOperationException: empty.reduceLeft
使用するコードの修正
myList foldLeft(List[String]()) {(a,b) => a+b}
1つの潜在的なオプションです。別の方法は、Optionラップされた結果を返すreduceLeftOption
バリアントを使用することです。
myList reduceLeftOption {(a,b) => a+b} match {
case None => // handle no result as necessary
case Some(v) => println(v)
}
両方がScala標準ライブラリにある基本的な理由は、おそらく両方がHaskell標準ライブラリ(foldl
とfoldl1
と呼ばれる)にあるためです。 reduceLeft
がそうでない場合は、別のプロジェクトで便利なメソッドとして定義されることがよくあります。
Scalaの関数型プログラミングの原則 (Martin Odersky)から:
関数
reduceLeft
は、より一般的な関数foldLeft
の観点から定義されています。
foldLeft
はreduceLeft
と似ていますが、空のリストでz
が呼び出されたときに返される追加パラメーターとして、accumulatorfoldLeft
を取ります。
(List (x1, ..., xn) foldLeft z)(op) = (...(z op x1) op ...) op x
[空のリストで呼び出されたときに例外をスローするreduceLeft
とは対照的。]
コース(講義5.5を参照)はこれらの関数の抽象的な定義を提供し、それらの違いを示しますが、パターンマッチングと再帰の使用法は非常に似ています。
abstract class List[T] { ...
def reduceLeft(op: (T,T)=>T) : T = this match{
case Nil => throw new Error("Nil.reduceLeft")
case x :: xs => (xs foldLeft x)(op)
}
def foldLeft[U](z: U)(op: (U,T)=>U): U = this match{
case Nil => z
case x :: xs => (xs foldLeft op(z, x))(op)
}
}
foldLeft
はU
型の値を返すことに注意してください。これは必ずしもList[T]
と同じ型である必要はありませんが、reduceLeftはリストと同じ型の値を返します。
Fold/reduceで何をしているのかを本当に理解するには、以下を確認してください。 http://wiki.tcl.tk/1798 非常に良い説明。フォールドの概念が得られると、reduceは上記の答えと一緒になります:list.tail.foldLeft(list.head)(_)