Уменьшить, свернуть или сканировать (влево/вправо)?

Когда следует использовать reduceLeft, reduceRight, foldLeft, foldRight, scanLeft или scanRight?

Я хочу интуицию/обзор их различий - возможно, с некоторыми простыми примерами.

Ответ 1

В общем, все 6-кратные функции применяют двоичный оператор к каждому элементу коллекции. Результат каждого шага передается на следующий шаг (в качестве ввода одному из двоичного оператора два аргумента). Таким образом, мы можем накопить результат.

reduceLeft и reduceRight кумуляция одного результата.

foldLeft и foldRight кумуляция одного результата с использованием начального значения.

scanLeft и scanRight накапливают совокупность промежуточных кумулятивных результатов с использованием начального значения.

накапливались

От LEFT и вперед...

С помощью набора элементов abc и двоичного оператора add мы можем исследовать, что делают различные функции фолда при переходе от элемента LEFT коллекции (от 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 и вернемся назад (от 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)

.

De-кумулятов

От LEFT и вперед...

Если вместо этого мы должны были декомпрессировать некоторый результат путем вычитания, начиная с элемента 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)


С ПРАВО и назад...

Но обратите внимание на изменения 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) 

Последний список (-2, 3, -1, 4, 0), возможно, не то, что вы бы интуитивно ожидали!

Как вы видите, вы можете проверить, что делает ваш foldX, просто запустив scanX и отлаживая накопленный результат на каждом шаге.

Нижняя строка

  • Скомпонуйте результат с помощью reduceLeft или reduceRight.
  • Скомпонуйте результат с помощью foldLeft или foldRight, если у вас есть начальное значение.
  • Кумуляция коллекции промежуточных результатов с помощью scanLeft или scanRight.

  • Используйте вариацию xLeft, если вы хотите перейти вперед через коллекцию.

  • Используйте переменную xRight, если вы хотите вернуться назад через коллекцию.

Ответ 2

Обычно метод REDUCE, FOLD, SCAN работает, накапливая данные по LEFT и продолжая изменять переменную RIGHT. Основное различие между ними: REDUCE, FOLD: -

Fold всегда начинается с значения seed, то есть определяемого пользователем начального значения. Сокращение будет вызывать исключение, если коллекция пуста, где в качестве сбрасывает значение семени. Всегда будет иметь одно значение.

Сканирование используется для некоторого порядка обработки элементов с левой или с правой стороны, тогда мы можем использовать предыдущий результат при последующем вычислении. Это означает, что мы можем сканировать элементы. Всегда будет выводить коллекцию.

  • Метод 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 Список сканирования (0, -2, -3, -3, -2, 0)

  • {0, -2} = > - 2 {-2, -1} = > - 3 {-3,0} = > - 3 {-3,1} = > - 2 {-2, 2} = > 0 scanLeft (a + b) Список (0, -2, -3, -3, -2, 0)

  • {0, -2} = > - 2 {-2, -1} = > - 3 {-3,0} = > - 3 {-3,1} = > - 2 {-2, 2} = > 0 scanLeft (b + a) Список (0, -2, -3, -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)

с помощью операций reduce, fold над списком строк List("A","B","C","D","E")

  • {A, B} = > AB {AB, C} = > ABC {ABC, D} = > ABCD {ABCD, E} = > ABCDE уменьшить (a + b) ABCDE
  • {A, B} = > AB {AB, C} = > ABC {ABC, D} = > ABCD {ABCD, E} = > ABCDE reduceLeft (a + b) ABCDE
  • {A, B} = > BA {BA, C} = > CBA {CBA, D} = > DCBA {DCBA, E} = > EDCBA reduceLeft (b + a) EDCB
  • {D, E} = > DE {C, DE} = > CDE {B, CDE} = > BCDE {A, BCDE} = > ABCDE reduceRight (a + b) ABCDE
  • {D, E} = > ED {C, ED} = > EDC {B, EDC} = > EDCB {A, EDCB} = > EDCBA reduceRight (b + a) EDCBA

Код:

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}))
}