縮小、折り畳み、スキャン(左/右)? 質問する

縮小、折り畳み、スキャン(左/右)? 質問する

reduceLeft、、、、reduceRightまたはfoldLeftはいつ使用すればよいですfoldRightか?scanLeftscanRight

それらの違いについての直感や概要を、できれば簡単な例とともに知りたいです。

ベストアンサー1

一般的に、6つのfold関数はすべて、コレクションの各要素に二項演算子を適用します。各ステップの結果は次のステップに渡されます(二項演算子の2つの引数の1つへの入力として)。このようにして、累積結果。

reduceLeftreduceRight単一の結果を累積します。

foldLeftfoldRight開始値を使用して単一の結果を累積します。

scanLeftscanRight開始値を使用して中間累積結果のコレクションを累積します。

蓄積する

左から前方へ...

要素のコレクション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


右から後ろへ...

右の要素から始めて、逆方向に(CからAへ)進んでいくと、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)

累積解除

左から前方へ...

もし私たちが代わりに累積を減らすコレクションの左の要素から減算して結果を取得するには、resバイナリ演算子の最初の引数を通じて結果を累積しますminus

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)


右から後ろへ...

しかし、xRightのバリエーションには注意してください。xRightのバリエーションの(非)累積値は、2番res二項演算子のパラメータminus:

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) 

最後のリスト (-2, 3, -1, 4, 0) は、おそらく直感的に予想されるものではないでしょう。

ご覧のとおり、単に scanX を実行して各ステップで累積された結果をデバッグするだけで、foldX が何を実行しているかを確認できます。

結論

  • reduceLeftまたはを使用して結果を累積しますreduceRight
  • 開始値がある場合は、foldLeftまたは を使用して結果を累積します。foldRight
  • scanLeftまたはを使用して中間結果のコレクションを累積しますscanRight

  • 行きたい場合はxLeftバリエーションを使用してください転送コレクションを通して。

  • 行きたい場合はxRightバリエーションを使用してください後ろ向きコレクションを通して。

おすすめ記事