reduceLeft,
reduceRight,
foldLeft,
foldRight,
scanLeft,
scanRight` はどのような場合に使用するのですか?
それらの違いを直感的に、あるいは簡単な例を挙げて概観したいと思います。
一般的に、6つのfold関数はすべて、コレクションの各要素に二項演算子を適用します。各ステップの結果は、(二項演算子の 2 つの引数のうちの 1 つの入力として)次のステップに渡されます。このようにして、結果を 累積 することができます。
reduceLeftと
reduceRight` は1つの結果を累積します。
foldLeftと
foldRight` は、開始値を使って1つの結果を累積します。
scanLeftと
scanRight`は、開始値を使って中間的な累積結果のコレクションを累積します。
左から順に...。
要素の集合体 abc
と二項演算子 add
を使って、集合体の左の要素から前に進むとき (A から C へ) に、異なる折り畳み関数がどのような動作をするかを調べてみましょう。
val abc = List("A", "B", "C")
def add(res: String, x: String) = {
println(s"op: $res + $x = ${res + x}")
res + x
}
abc.reduceLeft(add)
// op: A + B = AB
// op: AB + C = ABC // accumulates value AB in *first* operator arg `res`
// res: String = ABC
abc.foldLeft("z")(add) // with start value "z"
// op: z + A = zA // initial extra operation
// op: zA + B = zAB
// op: zAB + C = zABC
// res: String = zABC
abc.scanLeft("z")(add)
// op: z + A = zA // same operations as foldLeft above...
// op: zA + B = zAB
// op: zAB + C = zABC
// res: List[String] = List(z, zA, zAB, zABC) // maps intermediate results
となります。 RIGHT から逆方向に...。
RIGHTの要素から始めて逆に(CからAへ)進むと、2項演算子の*2番目の引数が結果を累積するようになっていることに気がつきます(演算子は同じで、役割を明確にするために引数の名前を入れ替えただけです)。
def add(x: String, res: String) = {
println(s"op: $x + $res = ${x + res}")
x + res
}
abc.reduceRight(add)
// op: B + C = BC
// op: A + BC = ABC // accumulates value BC in *second* operator arg `res`
// res: String = ABC
abc.foldRight("z")(add)
// op: C + z = Cz
// op: B + Cz = BCz
// op: A + BCz = ABCz
// res: String = ABCz
abc.scanRight("z")(add)
// op: C + z = Cz
// op: B + Cz = BCz
// op: A + BCz = ABCz
// res: List[String] = List(ABCz, BCz, Cz, z)
左から順に...左から順に
代わりに、コレクションの左端の要素から減算して結果を De-cumulate する場合は、バイナリ演算子 minus
の最初の引数 res
を通して結果を累積します。
val xs = List(1, 2, 3, 4)
def minus(res: Int, x: Int) = {
println(s"op: $res - $x = ${res - x}")
res - x
}
xs.reduceLeft(minus)
// op: 1 - 2 = -1
// op: -1 - 3 = -4 // de-cumulates value -1 in *first* operator arg `res`
// op: -4 - 4 = -8
// res: Int = -8
xs.foldLeft(0)(minus)
// op: 0 - 1 = -1
// op: -1 - 2 = -3
// op: -3 - 3 = -6
// op: -6 - 4 = -10
// res: Int = -10
xs.scanLeft(0)(minus)
// op: 0 - 1 = -1
// op: -1 - 2 = -3
// op: -3 - 3 = -6
// op: -6 - 4 = -10
// res: List[Int] = List(0, -1, -3, -6, -10)
となります。 RIGHTから逆に...。
しかし、今度はxRightのバリエーションに注目してください。xRightのバリエーションにおける(非)累積値は、バイナリ演算子minus
の2番目のパラメータres
に渡されることを覚えておいてください。
def minus(x: Int, res: Int) = {
println(s"op: $x - $res = ${x - res}")
x - res
}
xs.reduceRight(minus)
// op: 3 - 4 = -1
// op: 2 - -1 = 3 // de-cumulates value -1 in *second* operator arg `res`
// op: 1 - 3 = -2
// res: Int = -2
xs.foldRight(0)(minus)
// op: 4 - 0 = 4
// op: 3 - 4 = -1
// op: 2 - -1 = 3
// op: 1 - 3 = -2
// res: Int = -2
xs.scanRight(0)(minus)
// op: 4 - 0 = 4
// op: 3 - 4 = -1
// op: 2 - -1 = 3
// op: 1 - 3 = -2
// res: List[Int] = List(-2, 3, -1, 4, 0)
最後の List(-2, 3, -1, 4, 0) は、直感的に期待するものではないかもしれません。
このように、単純に scanX を実行して、各ステップの累積結果をデバッグすることで、 foldX が何をしているのかを確認することができます。
reduceLeftまたは
reduceRight` で結果を累積します。
開始値がある場合は foldLeft
または foldRight
で結果を累積します。
scanLeftまたは
scanRight` を使用して、中間結果のコレクションを累積します。
コレクションを 前方に 進めたい場合には xLeft バリエーションを使用してください。
コレクションを 後方 に移動したい場合には xRight のバリエーションを使用してください。
通常、REDUCE,FOLD,SCAN法は、左のデータを蓄積し、右の変数を変化させ続けることで機能します。REDUCE,FOLDの主な違いは次の通りです。
Fold は常に seed
値、つまりユーザー定義の開始値で開始します。
REDUCE,FOLDの主な違いは以下の通りです。常に単一の値が得られます。
Scanは、アイテムを左から右へと処理する際に使用されます。つまり、アイテムをスキャンすることができます。**結果は常にコレクションとなります。
LEFT_REDUCEメソッドはREDUCEメソッドと同様の動作をします。
RIGHT_REDUCEはreduceLeftとは逆で、RIGHTに値を蓄積し、左の変数を変化させ続けます。
reduceLeftOptionとreduceRightOptionはleft_reduceとright_reduceに似ていますが、OPTIONオブジェクトで結果を返す点が異なります。
以下のコードの出力の一部は :-) です。
数字のリストに対する scan
演算の使用(seed
値 0
を使用) List(-2,-1,0,1,2)
0,-2}=>-2 {-2,-1}=>-3 {-3,0}=>-3 {-3,1}=>-2 {-2,2}=>0 scan List(0, -2, -3, -2, 0)
0,2}=>-2 {-2,-1}=>-3 {-3,0}=>-3 {-3,1}=>-2 {-2,2}=>0 scanLeft (a+b) List(0, -2, -3, -2, 0)
0,2}=>-2 {-2,-1}=>-3 {-3,0}=>-3 {-3,1}=>-2 {-2,2}=>0 scanLeft (b+a) リスト(0, -2, -3, -2, 0)
2,0}=>2 {1,2}=>3 {0,3}=>3 {1,3}=>2 {2,2}=>0 scanRight (a+b) リスト(0, 2, 3, 3, 2, 0)
2,0}=>2 {1,2}=>3 {0,3}=>3 {-1,3}=>2 {-2,2}=>0 scanRight (b+a) リスト(0, 2, 3, 3, 2, 0)
文字列のリスト List("A","B","C","D","E")
に対して reduce
, fold
の操作を行う。
コード:
object ScanFoldReduce extends App {
val list = List("A","B","C","D","E")
println("reduce (a+b) "+list.reduce((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" ")
a+b
}))
println("reduceLeft (a+b) "+list.reduceLeft((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" ")
a+b
}))
println("reduceLeft (b+a) "+list.reduceLeft((a,b)=>{
print("{"+a+","+b+"}=>"+ (b+a)+" " )
b+a
}))
println("reduceRight (a+b) "+list.reduceRight((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" " )
a+b
}))
println("reduceRight (b+a) "+list.reduceRight((a,b)=>{
print("{"+a+","+b+"}=>"+ (b+a)+" ")
b+a
}))
println("scan "+list.scan("[")((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" " )
a+b
}))
println("scanLeft (a+b) "+list.scanLeft("[")((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" " )
a+b
}))
println("scanLeft (b+a) "+list.scanLeft("[")((a,b)=>{
print("{"+a+","+b+"}=>"+ (b+a)+" " )
b+a
}))
println("scanRight (a+b) "+list.scanRight("[")((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" " )
a+b
}))
println("scanRight (b+a) "+list.scanRight("[")((a,b)=>{
print("{"+a+","+b+"}=>"+ (b+a)+" " )
b+a
}))
//Using numbers
val list1 = List(-2,-1,0,1,2)
println("reduce (a+b) "+list1.reduce((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" ")
a+b
}))
println("reduceLeft (a+b) "+list1.reduceLeft((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" ")
a+b
}))
println("reduceLeft (b+a) "+list1.reduceLeft((a,b)=>{
print("{"+a+","+b+"}=>"+ (b+a)+" " )
b+a
}))
println(" reduceRight (a+b) "+list1.reduceRight((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" " )
a+b
}))
println(" reduceRight (b+a) "+list1.reduceRight((a,b)=>{
print("{"+a+","+b+"}=>"+ (b+a)+" ")
b+a
}))
println("scan "+list1.scan(0)((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" " )
a+b
}))
println("scanLeft (a+b) "+list1.scanLeft(0)((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" " )
a+b
}))
println("scanLeft (b+a) "+list1.scanLeft(0)((a,b)=>{
print("{"+a+","+b+"}=>"+ (b+a)+" " )
b+a
}))
println("scanRight (a+b) "+list1.scanRight(0)((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" " )
a+b}))
println("scanRight (b+a) "+list1.scanRight(0)((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" " )
b+a}))
}