В чем принципиальная разница между свертыванием и сокращением в Котлине? Когда какой использовать?

132

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

TapanHP
источник
4
Взгляните на это для глубокого фундаментального обсуждения этой темы
GhostCat
2
@LunarWatcher, я видел эти документы, но не понял, это вопрос, который вы задали, можете ли вы привести пример?
TapanHP
1
@MattKlein done
Джейсон Минард

Ответы:

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когда ваша операция не зависит от каких-либо значений, кроме значений в коллекции, к которой вы ее применяете.

zsmb13
источник
47
Хорошее объяснение! Я бы сказал также, что пустую коллекцию нельзя уменьшить, но можно свернуть.
Miha_x64 08
Видите ли, я на очень начальном уровне в Котлине, самый первый пример, который вы привели, можете ли вы объяснить его подробнее с помощью некоторых шагов и окончательного ответа? было бы большим подспорьем
TapanHP 08
3
@TapanHP emptyList<Int>().reduce { acc, s -> acc + s }выдаст исключение, но emptyList<Int>().fold(0) { acc, s -> acc + s }все в порядке.
Miha_x64 08
31
reduce также заставляет возвращать лямбда того же типа, что и члены списка, что неверно для fold. Это важное следствие создания первого элемента списка, начального значения аккумулятора.
andresp
4
@andresp: просто в качестве примечания для полноты: он не обязательно должен быть одного типа. Члены списка также могут быть подтипом аккумулятора: это действительно работает listOf<Int>(1, 2).reduce { acc: Number, i: Int -> acc.toLong() + i }(тип списка - Int, в то время как тип аккумулятора объявлен как Number и фактически является Long)
Борис
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)
Мэтт Кляйн
источник