1. ホーム
  2. scala

[解決済み] 縮小、折りたたみ、スキャン(左/右)?

2022-04-19 19:11:53

質問

を使用する必要があります。 reduceLeft , reduceRight , foldLeft , foldRight , scanLeft または scanRight ?

その違いについて、簡単な例を挙げて直感的に理解したいのです。

どのように解決するのですか?

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

reduceLeftreduceRight は1つの結果を計算します。

foldLeftfoldRight は、開始値を用いて一つの結果を計算します。

scanLeftscanRight は、開始値を用いて、中間的な累積結果の集合を計算する。

アキュムレート

LEFTとforwardsから...

要素の集まりで abc と二項演算子 add コレクションのLEFT要素から前方に進むとき(AからCまで)、異なるfold関数が何を行うかを調べることができます。

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)へ進むと、今度は 第二 の引数が結果を蓄積します(演算子は同じで、役割を明確にするために引数名を入れ替えただけです)。

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)

.

デキュムレーション

LEFTとforwardsから...

もし、その代わりに デキュムレーション コレクションの LEFT 要素から始まる引き算によって、ある結果を得るには、その結果を最初の引数 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)



RIGHTと逆から...

しかし、今、xRightのバリエーションに注目してください。xRight のバリエーションで(非)累積された値が 第二 パラメータ 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) 

最後のList(-2, 3, -1, 4, 0)は、もしかしたら直感的に期待するものとは違うかもしれませんね

このように、foldXの代わりにscanXを実行し、各ステップで累積した結果をデバッグするだけで、foldXが何をしているかを確認することができるのです。

ボトムライン

  • で結果を積算する reduceLeft または reduceRight .
  • で結果を積算する foldLeft または foldRight 開始値がある場合
  • で中間結果の集まりを集計する。 scanLeft または scanRight .

  • にしたい場合は、xLeftのバリエーションを使用します。 前方 をコレクションを通して表示します。

  • にしたい場合は、xRightのバリエーションを使用します。 後方 をコレクションを通して表示します。