Kurangi, lipat, atau pindai (Kiri / Kanan)?


187

Kapan saya harus menggunakan reduceLeft, reduceRight, foldLeft, foldRight, scanLeftatauscanRight ?

Saya ingin intuisi / gambaran perbedaan mereka - mungkin dengan beberapa contoh sederhana.



1
Terima kasih untuk penunjuknya. Ini sedikit di atas pengetahuan teknis saya :) Apakah ada sesuatu dalam jawaban saya yang menurut Anda harus diklarifikasi / diubah?
Marc Grue

Tidak, hanya menunjukkan sedikit sejarah dan relevansinya dengan MPP.
samthebest

Yah, secara tegas membedakan antara reducedan foldBUKAN keberadaan nilai awal - lebih merupakan konsekuensi dari alasan matematika yang mendasari yang lebih mendalam.
samthebest

Jawaban:


370

Secara umum, semua 6 fungsi lipat berlaku operator biner untuk setiap elemen koleksi. Hasil dari setiap langkah diteruskan ke langkah berikutnya (sebagai input ke salah satu dari dua argumen operator biner). Dengan cara ini kita bisa mengumpulkan hasilnya.

reduceLeft dan reduceRight mengumpulkan satu hasil.

foldLeft dan foldRight mengumpulkan satu hasil menggunakan nilai awal.

scanLeft dan scanRight mengumpulkan kumpulan hasil kumulatif antara menggunakan nilai awal.

Mengumpulkan

Dari KIRI dan ke depan ...

Dengan kumpulan elemen abcdan operator biner, addkita dapat menjelajahi apa fungsi lipatan yang berbeda dilakukan saat beralih dari elemen LEFT koleksi (dari A ke C):

val abc = List("A", "B", "C")

def add(res: String, x: String) = { 
  println(s"op: $res + $x = ${res + x}")
  res + x
}

abc.reduceLeft(add)
// op: A + B = AB
// op: AB + C = ABC    // accumulates value AB in *first* operator arg `res`
// res: String = ABC

abc.foldLeft("z")(add) // with start value "z"
// op: z + A = zA      // initial extra operation
// op: zA + B = zAB
// op: zAB + C = zABC
// res: String = zABC

abc.scanLeft("z")(add)
// op: z + A = zA      // same operations as foldLeft above...
// op: zA + B = zAB
// op: zAB + C = zABC
// res: List[String] = List(z, zA, zAB, zABC) // maps intermediate results


Dari KANAN dan mundur ...

Jika kita mulai dengan elemen KANAN dan mundur (dari C ke A) kita akan melihat bahwa sekarang yang kedua argumen ke operator biner kita mengakumulasi hasilnya (operatornya sama, kita hanya mengganti nama argumen untuk memperjelas peran mereka. ):

def add(x: String, res: String) = {
  println(s"op: $x + $res = ${x + res}")
  x + res
}

abc.reduceRight(add)
// op: B + C = BC
// op: A + BC = ABC  // accumulates value BC in *second* operator arg `res`
// res: String = ABC

abc.foldRight("z")(add)
// op: C + z = Cz
// op: B + Cz = BCz
// op: A + BCz = ABCz
// res: String = ABCz

abc.scanRight("z")(add)
// op: C + z = Cz
// op: B + Cz = BCz
// op: A + BCz = ABCz
// res: List[String] = List(ABCz, BCz, Cz, z)

.

De-kumulasi

Dari KIRI dan ke depan ...

Jika sebaliknya kami harus melakukan de-kumulasi beberapa hasil dengan mengurangi mulai dari elemen LEFT koleksi, kami akan mengumpulkan hasil melalui argumen pertama resdari operator biner kami minus:

val xs = List(1, 2, 3, 4)

def minus(res: Int, x: Int) = {
  println(s"op: $res - $x = ${res - x}")
  res - x
}

xs.reduceLeft(minus)
// op: 1 - 2 = -1
// op: -1 - 3 = -4  // de-cumulates value -1 in *first* operator arg `res`
// op: -4 - 4 = -8
// res: Int = -8

xs.foldLeft(0)(minus)
// op: 0 - 1 = -1
// op: -1 - 2 = -3
// op: -3 - 3 = -6
// op: -6 - 4 = -10
// res: Int = -10

xs.scanLeft(0)(minus)
// op: 0 - 1 = -1
// op: -1 - 2 = -3
// op: -3 - 3 = -6
// op: -6 - 4 = -10
// res: List[Int] = List(0, -1, -3, -6, -10)


Dari KANAN dan mundur ...

Tapi lihat variasi xRight sekarang! Ingat bahwa (de-) nilai terakumulasi dalam variasi xRight diteruskan ke parameter keduares dari operator biner kami minus:

def minus(x: Int, res: Int) = {
  println(s"op: $x - $res = ${x - res}")
  x - res
}

xs.reduceRight(minus)
// op: 3 - 4 = -1
// op: 2 - -1 = 3  // de-cumulates value -1 in *second* operator arg `res`
// op: 1 - 3 = -2
// res: Int = -2

xs.foldRight(0)(minus)
// op: 4 - 0 = 4
// op: 3 - 4 = -1
// op: 2 - -1 = 3
// op: 1 - 3 = -2
// res: Int = -2

xs.scanRight(0)(minus)
// op: 4 - 0 = 4
// op: 3 - 4 = -1
// op: 2 - -1 = 3
// op: 1 - 3 = -2
// res: List[Int] = List(-2, 3, -1, 4, 0) 

Daftar terakhir (-2, 3, -1, 4, 0) mungkin bukan yang Anda harapkan secara intuitif!

Seperti yang Anda lihat, Anda dapat memeriksa apa yang dilakukan foldX Anda dengan hanya menjalankan scanX dan men-debug hasil yang terakumulasi pada setiap langkah.

Intinya

  • Akumulasi hasil dengan reduceLeftataureduceRight .
  • Akumulasi hasil dengan foldLeftatau foldRightjika Anda memiliki nilai awal.
  • Mengumpulkan kumpulan hasil antara dengan scanLeftatau scanRight.

  • Gunakan variasi xLeft jika Anda ingin meneruskan koleksi.

  • Gunakan variasi xRight jika Anda ingin melihat kembali koleksi.

14
Jika saya tidak salah, versi kiri dapat menggunakan optimasi panggilan ekor, yang berarti jauh lebih efisien.
Trylks

3
@Marc, saya suka contoh dengan surat, itu membuat semuanya menjadi sangat jelas
Muhammad Farag

@Trylks foldRight juga dapat diimplementasikan dengan tailrec
Timothy Kim

@TimothyKim dapat melakukannya, dengan implementasi yang tidak langsung dioptimalkan untuk melakukannya. Misalnya Dalam kasus tertentu daftar Scala , cara itu terdiri dari membalikkan Listuntuk kemudian diterapkan foldLeft. Koleksi lain dapat menerapkan strategi yang berbeda. Secara umum, jika foldLeftdan foldRightdapat digunakan secara bergantian (properti asosiatif dari operator yang diterapkan), maka foldLeftlebih efisien dan disukai.
Trylks

9

Biasanya MENGURANGI, FOLD, metode SCAN bekerja dengan mengumpulkan data pada LEFT dan terus mengubah variabel KANAN. Perbedaan utama di antara mereka adalah REDUCE, FOLD adalah: -

Lipat akan selalu dimulai dengan seednilai yaitu nilai awal yang ditentukan pengguna. Reduce akan membuang pengecualian jika koleksi kosong di mana lipatan mengembalikan nilai benih. Akan selalu menghasilkan nilai tunggal.

Pemindaian digunakan untuk beberapa pemrosesan urutan item dari sisi kiri atau kanan, maka kita dapat menggunakan hasil sebelumnya dalam perhitungan selanjutnya. Itu artinya kita dapat memindai item. Akan selalu menghasilkan koleksi.

  • Metode LEFT_REDUCE bekerja mirip dengan Metode REDUCE.
  • RIGHT_REDUCE berlawanan dengan mengurangiLeft satu yaitu mengakumulasi nilai dalam KANAN dan terus mengubah variabel kiri.

  • MengurangiLeftOpsi dan MengurangiRightOpsi mirip dengan perbedaan hanya left_reduce dan right_reduce adalah mereka mengembalikan hasil dalam objek OPTION.

Bagian dari output untuk kode yang disebutkan di bawah ini adalah: -

menggunakan scanoperasi di atas daftar angka (menggunakan seednilai 0)List(-2,-1,0,1,2)

  • {0, -2} => - 2 {-2, -1} => - 3 {-3,0} => - 3 {-3,1} => - 2 {-2,2} => 0 Daftar pindaian (0, -2, -3, -3, -2, 0)

  • {0, -2} => - 2 {-2, -1} => - 3 {-3,0} => - 3 {-3,1} => - 2 {-2,2} => 0 scanLeft (a + b) Daftar (0, -2, -3, -3, -2, 0)

  • {0, -2} => - 2 {-2, -1} => - 3 {-3,0} => - 3 {-3,1} => - 2 {-2,2} => 0 scanLeft (b + a) Daftar (0, -2, -3, -3, -2, 0)

  • {2,0} => 2 {1,2} => 3 {0,3} => 3 {-1,3} => 2 {-2,2} => 0 scanRight (a + b) Daftar ( 0, 2, 3, 3, 2, 0)

  • {2,0} => 2 {1,2} => 3 {0,3} => 3 {-1,3} => 2 {-2,2} => 0 scanRight (b + a) Daftar ( 0, 2, 3, 3, 2, 0)

menggunakan reduce, foldoperasi atas daftar StringsList("A","B","C","D","E")

  • {A, B} => AB {AB, C} => ABC {ABC, D} => ABCD {ABCD, E} => ABCDE mengurangi (a + b) ABCDE
  • {A, B} => AB {AB, C} => ABC {ABC, D} => ABCD {ABCD, E} => ABCDE mengurangiLeft (a + b) ABCDE
  • {A, B} => BA {BA, C} => CBA {CBA, D} => DCBA {DCBA, E} => EDCBA mengurangiLeft (b + a) EDCB
  • {D, E} => DE {C, DE} => CDE {B, CDE} => BCDE {A, BCDE} => Pengurangan ABCDERight (a + b) ABCDE
  • {D, E} => ED {C, ED} => EDC {B, EDC} => EDCB {A, EDCB} => Pengurangan EDCBA (b + a) EDCBA

Kode:

object ScanFoldReduce extends App {

    val list = List("A","B","C","D","E")
            println("reduce (a+b) "+list.reduce((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  ")
                a+b
            }))

            println("reduceLeft (a+b) "+list.reduceLeft((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  ")
                a+b
            }))

            println("reduceLeft (b+a) "+list.reduceLeft((a,b)=>{
                print("{"+a+","+b+"}=>"+ (b+a)+"  " )
                b+a
            }))

            println("reduceRight (a+b) "+list.reduceRight((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  " )
                a+b
            }))

            println("reduceRight (b+a) "+list.reduceRight((a,b)=>{
                print("{"+a+","+b+"}=>"+ (b+a)+"  ")
                b+a
            }))

            println("scan            "+list.scan("[")((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  " )
                a+b
            }))
            println("scanLeft (a+b)  "+list.scanLeft("[")((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  " )
                a+b
            }))
            println("scanLeft (b+a)  "+list.scanLeft("[")((a,b)=>{
                print("{"+a+","+b+"}=>"+ (b+a)+"  " )
                b+a
            }))
            println("scanRight (a+b) "+list.scanRight("[")((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  " )
                a+b
            }))
            println("scanRight (b+a) "+list.scanRight("[")((a,b)=>{
                print("{"+a+","+b+"}=>"+ (b+a)+"  " )
                b+a
            }))
//Using numbers
     val list1 = List(-2,-1,0,1,2)

            println("reduce (a+b) "+list1.reduce((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  ")
                a+b
            }))

            println("reduceLeft (a+b) "+list1.reduceLeft((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  ")
                a+b
            }))

            println("reduceLeft (b+a) "+list1.reduceLeft((a,b)=>{
                print("{"+a+","+b+"}=>"+ (b+a)+"  " )
                b+a
            }))

            println("      reduceRight (a+b) "+list1.reduceRight((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  " )
                a+b
            }))

            println("      reduceRight (b+a) "+list1.reduceRight((a,b)=>{
                print("{"+a+","+b+"}=>"+ (b+a)+"  ")
                b+a
            }))

            println("scan            "+list1.scan(0)((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  " )
                a+b
            }))

            println("scanLeft (a+b)  "+list1.scanLeft(0)((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  " )
                a+b
            }))

            println("scanLeft (b+a)  "+list1.scanLeft(0)((a,b)=>{
                print("{"+a+","+b+"}=>"+ (b+a)+"  " )
                b+a
            }))

            println("scanRight (a+b)         "+list1.scanRight(0)((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  " )
                a+b}))

            println("scanRight (b+a)         "+list1.scanRight(0)((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  " )
                b+a}))
}

9
Posting ini hampir tidak dapat dibaca. Harap perpendek kalimat, gunakan kata kunci nyata (mis. KurangiLeft alih-alih LEFT_REDUCE). Gunakan panah matematika nyata, tag kode ketika Anda berurusan dengan kode. Lebih suka contoh input / output daripada menjelaskan semuanya. Perhitungan menengah membuatnya sulit dibaca.
Mikaël Mayer

4

Untuk koleksi x dengan elemen x0, x1, x2, x3 dan fungsi arbitrer untuk Anda memiliki yang berikut:

1. x.reduceLeft    (f) is f(f(f(x0,x1),x2),x3) - notice 3 function calls
2. x.reduceRight   (f) is f(f(f(x3,x2),x1),x0) - notice 3 function calls
3. x.foldLeft (init,f) is f(f(f(f(init,x0),x1),x2),x3) - notice 4 function calls
4. x.foldRight(init,f) is f(f(f(f(init,x3),x2),x1),x0) - notice 4 function calls
5. x.scanLeft (init,f) is f(init,x0)=g0
                          f(f(init,x0),x1) = f(g0,x1) = g1
                          f(f(f(init,x0),x1),x2) = f(g1,x2) = g2
                          f(f(f(f(init,x0),x1),x2),x3) = f(g2,x3) = g3
                          - notice 4 function calls but also 4 emitted values
                          - last element is identical with foldLeft
6. x.scanRight (init,f) is f(init,x3)=h0
                          f(f(init,x3),x2) = f(h0,x2) = h1
                          f(f(f(init,x3),x2),x1) = f(h1,x1) = h2
                          f(f(f(f(init,x3),x2),x1),x0) = f(h2,x0) = h3
                          - notice 4 function calls but also 4 emitted values
                          - last element is identical with foldRight

Kesimpulannya

  • scanseperti foldtetapi juga memancarkan semua nilai perantara
  • reduce tidak memerlukan nilai awal yang kadang-kadang sedikit lebih sulit ditemukan
  • fold membutuhkan nilai awal yang sedikit lebih sulit ditemukan:
    • 0 untuk penjumlahan
    • 1 untuk produk
    • elemen pertama untuk min (beberapa mungkin menyarankan Integer.MAX_VALUE)
  • tidak 100% yakin tetapi sepertinya ada implementasi yang setara ini:
    • x.reduceLeft(f) === x.drop(1).foldLeft(x.head,f)
    • x.foldRight(init,f) === x.reverse.foldLeft(init,f)
    • x.foldLeft(init,f) === x.scanLeft(init,f).last
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.