Apa perbedaan mendasar antara lipatan dan pengurangan di Kotlin? Kapan menggunakan yang mana?

131

Saya cukup bingung dengan kedua fungsi ini fold()dan reduce()di Kotlin, adakah yang bisa memberi saya contoh konkret yang membedakan keduanya?

TapanHP
sumber
2
lipat dan kurangi .
Zoe
4
Lihatlah ini untuk diskusi mendasar yang mendalam tentang topik ini
GhostCat
2
@ LunarWatcher, saya melihat dokumen itu, tetapi tidak mendapatkannya, itu pertanyaan yang diposting, bisakah Anda memberi contoh?
TapanHP
1
@MattKlein selesai
Jayson Minard

Jawaban:

281

fold mengambil nilai awal, dan permintaan pertama dari lambda yang Anda lewati akan menerima nilai awal dan elemen pertama dari koleksi sebagai parameter.

Misalnya, ambil kode berikut yang menghitung jumlah daftar bilangan bulat:

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

Panggilan pertama ke lambda akan dengan parameter 0dan 1.

Memiliki kemampuan untuk memberikan nilai awal berguna jika Anda harus memberikan semacam nilai atau parameter default untuk operasi Anda. Misalnya, jika Anda mencari nilai maksimum di dalam daftar, tetapi karena alasan tertentu ingin mengembalikan setidaknya 10, Anda dapat melakukan hal berikut:

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

reducetidak mengambil nilai awal, melainkan mulai dengan elemen pertama koleksi sebagai akumulator (disebut sumdalam contoh berikut).

Sebagai contoh, mari kita lakukan jumlah bilangan bulat lagi:

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

Panggilan pertama ke lambda di sini akan dengan parameter 1dan 2.

Anda dapat menggunakannya reducesaat operasi Anda tidak bergantung pada nilai apa pun selain dari yang ada di koleksi yang Anda lamar.

zsmb13
sumber
47
Penjelasan yang bagus! Saya akan mengatakan juga, bahwa koleksi kosong tidak dapat dikurangi, tetapi dapat dilipat.
Miha_x64
lihat, m pada level paling pemula di Kotlin, contoh pertama yang Anda berikan dapat Anda jelaskan lebih banyak dengan beberapa langkah, dan jawaban akhir? akan sangat membantu
TapanHP
3
@TapanHP emptyList<Int>().reduce { acc, s -> acc + s }akan menghasilkan pengecualian, tetapi emptyList<Int>().fold(0) { acc, s -> acc + s }tidak masalah.
Miha_x64
31
mengurangi juga memaksa kembalinya lambda menjadi tipe yang sama dengan anggota daftar, yang tidak benar dengan lipatan. Ini adalah konsekuensi penting dari pembuatan elemen pertama dari daftar, nilai awal akumulator.
andresp
4
@andresp: sama seperti catatan untuk kelengkapan: tidak harus dengan tipe yang sama . Anggota daftar juga dapat menjadi subtipe akumulator: ini berfungsi listOf<Int>(1, 2).reduce { acc: Number, i: Int -> acc.toLong() + i }(daftar-jenisnya Int sedangkan tipe akumulator dinyatakan sebagai Angka dan sebenarnya Panjang)
Boris
11

Perbedaan fungsional utama yang saya sebut (yang disebutkan dalam komentar pada jawaban lain, tetapi mungkin sulit dimengerti) adalah bahwa reduce akan mengeluarkan pengecualian jika dilakukan pada koleksi kosong.

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

Ini karena .reducetidak tahu nilai apa yang dikembalikan jika "tidak ada data".

Bandingkan dengan ini .fold, yang mengharuskan Anda untuk memberikan "nilai awal", yang akan menjadi nilai default jika koleksi kosong:

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

Jadi, bahkan jika Anda tidak ingin mengagregasi koleksi Anda ke satu elemen dari tipe (tidak terkait) yang berbeda (yang hanya .foldakan membiarkan Anda melakukannya), jika koleksi awal Anda mungkin kosong maka Anda harus memeriksa koleksi Anda ukuran dulu dan kemudian .reduce, atau gunakan saja.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
sumber