Qual é a diferença básica entre fold e reduzir no Kotlin? Quando usar qual?

131

Estou bastante confuso com essas duas funções fold()e reduce()no Kotlin, alguém pode me dar um exemplo concreto que distinga as duas?

TapanHP
fonte
2
dobre e reduza .
Zoe
4
Ter um olhar para este para uma discussão profunda fundamentais deste tópico
GhostCat
2
@LunarWatcher, eu vi esses documentos, mas não consegui-lo, isso é uma pergunta postada, você pode dar um exemplo?
precisa saber é o seguinte
1
@MattKlein done
Jayson Minard

Respostas:

281

fold recebe um valor inicial e a primeira chamada do lambda que você passar para ele receberá esse valor inicial e o primeiro elemento da coleção como parâmetros.

Por exemplo, considere o seguinte código que calcula a soma de uma lista de números inteiros:

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

A primeira chamada para o lambda será com parâmetros 0e 1.

Ter a capacidade de transmitir um valor inicial é útil se você precisar fornecer algum tipo de valor ou parâmetro padrão para sua operação. Por exemplo, se você estava procurando o valor máximo dentro de uma lista, mas por algum motivo deseja retornar pelo menos 10, você pode fazer o seguinte:

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

reducenão assume um valor inicial, mas começa com o primeiro elemento da coleção como acumulador (chamado sumno exemplo a seguir).

Por exemplo, vamos fazer uma soma de números inteiros novamente:

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

A primeira chamada para o lambda aqui será com os parâmetros 1e 2.

Você pode usar reducequando sua operação não depende de nenhum outro valor além daqueles da coleção em que você está aplicando.

zsmb13
fonte
47
Boa explicação! Eu diria também que a coleção vazia não pode ser reduzida, mas pode ser dobrada.
Miha_x64
veja, no nível iniciante do Kotlin, o primeiro exemplo que você deu pode explicar mais com alguns passos e a resposta final? seria uma grande ajuda
TapanHP
3
O @TapanHP emptyList<Int>().reduce { acc, s -> acc + s }produzirá uma exceção, mas emptyList<Int>().fold(0) { acc, s -> acc + s }está OK.
Miha_x64
31
reduzir também força o retorno do lambda a ser do mesmo tipo que os membros da lista, o que não ocorre com fold. Essa é uma consequência importante de tornar o primeiro elemento da lista, o valor inicial do acumulador.
andresp
4
@andresp: apenas como uma observação para completar: ele não precisa ser do mesmo tipo. Os membros da lista também pode ser um subtipo do acumulador: isso não funciona listOf<Int>(1, 2).reduce { acc: Number, i: Int -> acc.toLong() + i }(a lista do tipo é Int enquanto o tipo acumulador é declarado como Número e, na verdade, é uma Long)
Boris
11

A principal diferença funcional que eu chamaria (mencionada nos comentários da outra resposta, mas pode ser difícil de entender) é que reduce lançará uma exceção se executada em uma coleção vazia.

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

Isso ocorre porque .reducenão sabe qual valor retornar no caso de "sem dados".

Compare isso com .fold, que exige que você forneça um "valor inicial", que será o valor padrão no caso de uma coleção vazia:

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

Portanto, mesmo que você não queira agregar sua coleção a um único elemento de um tipo diferente (não relacionado) (o que só .foldpermitirá), se a coleção inicial estiver vazia, verifique a coleção tamanho primeiro e depois .reduce, ou apenas use.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)
Matt Klein
fonte