У чому полягає основна різниця між складанням і зменшенням у Котліні? Коли використовувати який?


132

Я дуже плутаю це з обома функціями fold()і reduce()в Котліні, чи може хтось надати мені конкретний приклад, який відрізняє їх обох?



4
Погляньте на це, щоб глибоко обговорити цю тему
GhostCat

2
@LunarWatcher, я бачив ці документи, але не отримував їх, ось це розміщене питання, чи можете ви навести приклад?
TapanHP

1
@MattKlein виконано
Джейсон Мінард

Відповіді:


281

fold приймає початкове значення, і перший виклик лямбда, який ви передаєте йому, отримає це початкове значення та перший елемент колекції як параметри.

Наприклад, візьміть такий код, який обчислює суму списку цілих чисел:

listOf(1, 2, 3).fold(0) { sum, element -> sum + element }

Перший виклик лямбда буде з параметрами 0і 1.

Можливість передавати початкове значення корисно, якщо вам потрібно вказати якесь значення або параметр за замовчуванням для вашої операції. Наприклад, якщо ви шукали максимальне значення всередині списку, але чомусь хочете повернути щонайменше 10, ви можете зробити наступне:

listOf(1, 6, 4).fold(10) { max, element ->
    if (element > max) element else max
}

reduceне приймає початкового значення, а замість цього починається з першого елемента колекції як акумулятора (називається sumу наступному прикладі).

Наприклад, давайте знову зробимо суму цілих чисел:

listOf(1, 2, 3).reduce { sum, element -> sum + element }

Перший виклик лямбда тут буде з параметрами 1і 2.

Ви можете використовувати, reduceколи ваша робота не залежить від будь-яких значень, окрім значень у колекції, до якої ви її застосовуєте.


47
Гарне пояснення! Я б також сказав, що порожню колекцію неможливо зменшити, але її можна скласти.
Miha_x64

бачте, я на початковому рівні в Котліні, перший приклад, який ви навели, чи можете ви пояснити це декількома кроками та остаточною відповіддю? буде дуже
корисно

3
@TapanHP emptyList<Int>().reduce { acc, s -> acc + s }створить виняток, але emptyList<Int>().fold(0) { acc, s -> acc + s }це нормально.
Miha_x64

31
зменшити також змушує повернення лямбда того ж типу, що і члени списку, що не відповідає дійсності. Це важливий наслідок складання першого елемента списку, початкового значення акумулятора.
andresp

4
@andresp: як замітка про повноту: він не повинен бути одного типу. Учасники списку також можуть бути підтипом акумулятора: це спрацьовує listOf<Int>(1, 2).reduce { acc: Number, i: Int -> acc.toLong() + i }(тип списку - Int, тоді як тип акумулятора оголошено як число і насправді є довгим)
Борис

11

Основна функціональна відмінність, яку я хотів би зауважити (про яку йдеться в коментарях до іншої відповіді, але може бути важко зрозуміти), полягає в тому, що reduce буде винятком виключення, якщо воно буде виконане на порожній колекції.

listOf<Int>().reduce { x, y -> x + y }
// java.lang.UnsupportedOperationException: Empty collection can't be reduced.

Це тому, .reduceщо не знає, яке значення повернути у випадку "немає даних".

Порівнюйте це з тим .fold, що вимагає надати "початкове значення", яке буде значенням за замовчуванням у випадку порожньої колекції:

val result = listOf<Int>().fold(0) { x, y -> x + y }
assertEquals(0, result)

Отже, навіть якщо ви не хочете об'єднати свою колекцію до одного елемента іншого (не пов'язаного) типу (який лише .foldдозволить вам робити), якщо ваша початкова колекція може бути порожньою, ви повинні або перевірити свою колекцію розмір спочатку, а потім .reduce, або просто використовувати.fold

val collection: List<Int> = // collection of unknown size

val result1 = if (collection.isEmpty()) 0
              else collection.reduce { x, y -> x + y }

val result2 = collection.fold(0) { x, y -> x + y }

assertEquals(result1, result2)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.