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


187

Коли я повинен використовувати reduceLeft, reduceRight, foldLeft, foldRight, scanLeftабо scanRight?

Я хочу інтуїцію / огляд їх відмінностей - можливо, з простими прикладами.


Рекомендуємо Вам ознайомитись з stackoverflow.com/questions/25158780 / ...
samthebest

1
Дякуємо за вказівник. Це трохи вище моїх технічних знань :) Чи є щось у моїй відповіді, що, на вашу думку, слід уточнити / змінити?
Марк Грю

Ні, лише зазначивши трохи історії та актуальності для MPP.
samthebest

Ну, чітко кажучи, відмінність між reduceі foldНЕ є початковим значенням - скоріше це є наслідком більш глибокої основної математичної причини.
samthebest

Відповіді:


370

Загалом, всі 6-кратні функції застосовують двійковий оператор до кожного елемента колекції. Результат кожного кроку передається на наступний крок (як вхід до одного з двох аргументів двійкового оператора). Таким чином ми можемо накопичити результат.

reduceLeftі reduceRightпідсумувати єдиний результат.

foldLeftі foldRightкумулювати єдиний результат, використовуючи стартове значення.

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

Накопичуватися

Зліва і вперед ...

За допомогою набору елементів abcта двійкового оператора addми можемо дослідити, що виконують різні функції складання, коли рухаються вперед від лівого елемента колекції (від А до С):

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), ми помітимо, що тепер другий аргумент для нашого бінарного оператора накопичує результат (оператор той самий, ми просто переключили назви аргументів, щоб зрозуміти їх ролі ):

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 передається другому параметру 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, якщо хочете повернутися назад по колекції.

14
Якщо я не помиляюся, ліва версія може використовувати оптимізацію хвостових дзвінків, а це означає, що вона набагато ефективніша.
Trylks

3
@Marc, мені подобаються приклади з листами, це дуже зрозуміло
Мухаммед Фараг

@Trylks foldRight також може бути реалізований разом з tailrec
Тімоті Кім

@TimothyKim це може, з непростими реалізаціями, оптимізованими для цього. Наприклад, у конкретному випадку списків Scala цей спосіб полягає у тому, Listщоб змінити значення, яке слід застосувати foldLeft. Інші колекції можуть реалізовувати різні стратегії. Взагалі, якщо foldLeftі foldRightможна використовувати взаємозамінно (асоціативна властивість застосованого оператора), то foldLeftце більш ефективно та бажано.
Trylks

9

Зазвичай метод REDUCE, FOLD, SCAN працює, накопичуючи дані вліво та продовжуючи змінювати змінну RIGHT. Основна відмінність між ними - ЗНИЖЕННЯ, FOLD:

Fold завжди починається зі seedзначення, тобто початкового значення, визначеного користувачем. Зменшення призведе до виключення, якщо колекція буде порожньою, коли як складка повертає значення насіння. Завжди призведе до єдиного значення.

Сканування використовується для певного порядку обробки елементів з лівої чи правої сторони, тоді ми можемо скористатися попереднім результатом при наступному обчисленні. Це означає, що ми можемо сканувати предмети. Завжди буде результатом колекції.

  • Метод LEFT_REDUCE працює аналогічно методу REDUCE.
  • RIGHT_REDUCE є протилежною reduLeft one, тобто накопичує значення в RIGHT та продовжує змінювати ліву змінну.

  • ReduLeftOption і ReduRightOption схожі на 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 сканування Право (a + b) Список ( 0, 2, 3, 3, 2, 0)

  • {2,0} => 2 {1,2} => 3 {0,3} => 3 {-1,3} => 2 {-2,2} => 0 скануванняВправо (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 reduLeft (a + b) ABCDE
  • {A, B} => BA {BA, C} => CBA {CBA, D} => DCBA {DCBA, E} => EDCBA reduLeft (b + a) EDCB
  • {D, E} => DE {C, DE} => CDE {B, CDE} => BCDE {A, BCDE} => ABCDE reduRight (a + b) ABCDE
  • {D, E} => ED {C, ED} => EDC {B, EDC} => EDCB {A, EDCB} => EDCBA reduRight (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}))
}

9
Ця публікація ледве читається. Скоротіть речення, використовуйте реальні ключові слова (наприклад, зменшитиLeft замість LEFT_REDUCE). Використовуйте справжні математичні стрілки, кодові теги, коли ви маєте справу з кодом. Віддавайте перевагу прикладів введення / виведення, а не пояснюючи все. Проміжні обчислення важко читають.
Mikaël Mayer

4

Для колекції x з елементами x0, x1, x2, x3 та довільною функцією f у вас є наступне:

1. x.reduceLeft    (f) is f(f(f(x0,x1),x2),x3) - notice 3 function calls
2. x.reduceRight   (f) is f(f(f(x3,x2),x1),x0) - notice 3 function calls
3. x.foldLeft (init,f) is f(f(f(f(init,x0),x1),x2),x3) - notice 4 function calls
4. x.foldRight(init,f) is f(f(f(f(init,x3),x2),x1),x0) - notice 4 function calls
5. x.scanLeft (init,f) is f(init,x0)=g0
                          f(f(init,x0),x1) = f(g0,x1) = g1
                          f(f(f(init,x0),x1),x2) = f(g1,x2) = g2
                          f(f(f(f(init,x0),x1),x2),x3) = f(g2,x3) = g3
                          - notice 4 function calls but also 4 emitted values
                          - last element is identical with foldLeft
6. x.scanRight (init,f) is f(init,x3)=h0
                          f(f(init,x3),x2) = f(h0,x2) = h1
                          f(f(f(init,x3),x2),x1) = f(h1,x1) = h2
                          f(f(f(f(init,x3),x2),x1),x0) = f(h2,x0) = h3
                          - notice 4 function calls but also 4 emitted values
                          - last element is identical with foldRight

На закінчення

  • scanце як, foldале також випромінює всі проміжні значення
  • reduce не потрібно початкового значення, яке іноді знайти трохи складніше
  • fold потребує початкового значення, яке трохи важче знайти:
    • 0 для сум
    • 1 для продуктів
    • перший елемент для min (деякі можуть запропонувати Integer.MAX_VALUE)
  • не на 100% впевнений, але, схоже, є такі еквівалентні реалізації:
    • x.reduceLeft(f) === x.drop(1).foldLeft(x.head,f)
    • x.foldRight(init,f) === x.reverse.foldLeft(init,f)
    • x.foldLeft(init,f) === x.scanLeft(init,f).last
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.