Qual'è la differenza fondamentale tra fold e reduce in Kotlin? Quando usare quale?


131

Sono piuttosto confuso con queste due funzioni fold()e reduce()in Kotlin qualcuno può darmi un esempio concreto che le distingua entrambe?



4
Dai

2
@LunarWatcher, ho visto quei documenti, ma non capisco, è una domanda postata, puoi fare un esempio?
TapanHP,

1
@MattKlein done
Jayson Minard

Risposte:


281

fold accetta un valore iniziale e la prima invocazione della lambda che si passa ad essa riceverà quel valore iniziale e il primo elemento della raccolta come parametri.

Ad esempio, prendi il seguente codice che calcola la somma di un elenco di numeri interi:

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

La prima chiamata a lambda sarà con parametri 0e 1.

La possibilità di trasferire un valore iniziale è utile se è necessario fornire una sorta di valore o parametro predefinito per l'operazione. Ad esempio, se si cercava il valore massimo all'interno di un elenco, ma per qualche motivo si desidera restituire almeno 10, è possibile effettuare le seguenti operazioni:

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

reducenon accetta un valore iniziale, ma inizia invece con il primo elemento della raccolta come accumulatore (chiamato sumnell'esempio seguente).

Ad esempio, facciamo di nuovo una somma di numeri interi:

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

La prima chiamata al lambda qui sarà con parametri 1e 2.

È possibile utilizzare reducequando l'operazione non dipende da valori diversi da quelli della raccolta a cui la si applica.


47
Buona spiegazione Direi anche che quella collezione vuota non può essere ridotta, ma può essere piegata.
Miha_x64,

vedi, m a livello principiante a Kotlin, il primo esempio che hai dato puoi spiegarlo di più con alcuni passaggi e la risposta finale? sarebbe di grande aiuto
TapanHP,

3
@TapanHP emptyList<Int>().reduce { acc, s -> acc + s }produrrà un'eccezione, ma emptyList<Int>().fold(0) { acc, s -> acc + s }è OK.
Miha_x64,

31
riduci impone inoltre che il ritorno del lambda sia dello stesso tipo dei membri dell'elenco, il che non è vero con fold. Questa è una conseguenza importante della creazione del primo elemento dell'elenco, il valore iniziale dell'accumulatore.
Andresp,

4
@andresp: proprio come una nota di completezza: non deve essere dello stesso tipo. I membri della lista possono anche essere un sottotipo dell'accumulatore: questo funziona listOf<Int>(1, 2).reduce { acc: Number, i: Int -> acc.toLong() + i }(il tipo di lista è Int mentre il tipo di accumulatore viene dichiarato come Numero e in realtà è un Long)
Boris,

11

La principale differenza funzionale che vorrei evidenziare (che è menzionata nei commenti sull'altra risposta, ma potrebbe essere difficile da capire) è che reduce genererà un'eccezione se eseguita su una raccolta vuota.

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

Questo perché .reducenon sa quale valore restituire in caso di "nessun dato".

In contrasto con ciò .fold, che richiede di fornire un "valore iniziale", che sarà il valore predefinito in caso di una raccolta vuota:

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

Quindi, anche se non vuoi aggregare la tua raccolta fino a un singolo elemento di un tipo diverso (non correlato) (che .foldti consentirà solo di farlo), se la tua raccolta iniziale potrebbe essere vuota, devi controllare la tua raccolta prima le dimensioni e poi .reduce, o semplicemente usare.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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.