Küçültme, katlama veya tarama (Sol / Sağ)?


187

Ne zaman kullanmalıyım reduceLeft, reduceRight, foldLeft, foldRight, scanLeftveya scanRight?

Muhtemelen bazı basit örneklerle, farklılıklarına bir sezgi / genel bakış istiyorum.


Tavsiye etmenizi tavsiye ederim stackoverflow.com/questions/25158780/…
samthebest

1
İşaretçi için teşekkürler. Teknik bilgimin biraz üstünde :) Cevabımda netleştirilmesi / değiştirilmesi gerektiğini düşündüğünüz bir şey var mı?
Marc Grue

Hayır, sadece biraz tarih ve MPP ile ilgisini işaret ediyor.
samthebest

Peki, kesinlikle bir başlangıç ​​değerinin varlığı olan reduceve foldDEĞİLDİR, bu daha derinden yatan bir matematiksel nedenin bir sonucudur .
samthebest

Yanıtlar:


370

Genel olarak, 6 kat fonksiyonun tümü bir koleksiyonun her elemanına bir ikili operatör uygular. Her adımın sonucu bir sonraki adıma aktarılır (ikili operatörün iki bağımsız değişkeninden birine girdi olarak). Bu şekilde bir sonucu biriktirebiliriz .

reduceLeftve reduceRighttek bir sonuç biriktirir.

foldLeftve foldRightbir başlangıç ​​değeri kullanarak tek bir sonucu biriktirin.

scanLeftve scanRightbir başlangıç ​​değeri kullanarak bir ara kümülatif sonuç koleksiyonunu biriktirin.

Biriktirmek

SOL ve ileriye doğru ...

Bir elemanlar koleksiyonu abcve ikili bir operatörle add, koleksiyonun LEFT elemanından (A'dan C'ye) ilerlerken farklı katlama işlevlerinin ne yaptığını keşfedebiliriz:

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


SAĞ ve geriye doğru ...

SAĞ öğeyle başlayıp geriye doğru (C'den A'ya) gidersek, şimdi ikili operatörünüzün ikinci argümanının sonucu biriktirdiğini fark edeceğiz (operatör aynı, rollerini netleştirmek için argüman adlarını değiştirdik. ):

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-kümülat

SOL ve ileriye doğru ...

Bunun yerine biz olduğumuzu de-kümülat , ilk argüman yoluyla sonucunu kümüle verecek bir koleksiyonun SOL elemanından başlayarak çıkarılması ile bazı sonuç resbizim ikili operatörün 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)


SAĞ ve geriye doğru ...

Ama şimdi xRight varyasyonlarına dikkat edin! XRight varyasyonlarındaki (birikmiş) değerin ikili operatörümüzün ikinci parametresine iletildiğini unutmayın :resminus

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) 

Son Liste (-2, 3, -1, 4, 0) belki de sezgisel olarak beklediğiniz şey değil!

Gördüğünüz gibi, bunun yerine bir scanX çalıştırarak foldX'inizin ne yaptığını kontrol edebilir ve her adımda birikmiş sonuçta hata ayıklayabilirsiniz.

Sonuç olarak

  • Bir sonucu reduceLeftveya ile toplayın reduceRight.
  • Başlangıç ​​değerinizle foldLeftveya foldRightbaşlangıç ​​değeriniz varsa bir sonucu biriktirin .
  • Ara sonuçların bir koleksiyonunu scanLeftveya ile toplayın scanRight.

  • Koleksiyonda ileri gitmek istiyorsanız bir xLeft varyasyonu kullanın .

  • Koleksiyonda geriye doğru gitmek istiyorsanız xRight varyasyonunu kullanın .

14
Yanılmıyorsam, sol sürüm kuyruk çağrısı optimizasyonunu kullanabilir, bu da çok daha verimli olduğu anlamına gelir.
Trylks

3
@Marc, harflerle örnekleri seviyorum, işleri çok netleştirdi
Muhammad Farag

@Trylks foldRight, tailrec ile de uygulanabilir
Timothy Kim

@TimothyKim bunu yapmak için optimize edilmiş basit olmayan uygulamalarla yapabilir. Örneğin , Scala listelerinin özel durumunda , bu yol, Listdaha sonra uygulanacak olanın tersine çevrilmesinden ibarettir foldLeft. Diğer koleksiyonlar farklı stratejiler uygulayabilir. Genel olarak, eğer foldLeftve foldRightbirbirinin yerine kullanılabiliyorsa (uygulanan operatörün ilişkisel özelliği), foldLeftdaha verimli ve tercih edilir.
Trylks

9

Normalde SOLA, FOLD, SCAN yöntemi SOL veriyi toplayarak çalışır ve SAĞ değişkenini değiştirmeye devam eder. Aralarındaki temel fark REDÜCE, FOLD: -

Fold her zaman bir seeddeğerle başlar, yani kullanıcı tanımlı başlangıç ​​değeri. Toplama boşsa azaltma bir istisna atar, burada katlama tohum değerini geri verir. Her zaman tek bir değer verir.

Tarama, öğelerin sol veya sağ taraftan bazı işleme sıralaması için kullanılır, ardından sonraki hesaplamada önceki sonucu kullanabiliriz. Bu, öğeleri tarayabileceğimiz anlamına gelir. Her zaman bir koleksiyonla sonuçlanır.

  • LEFT_REDUCE yöntemi, REDUCE yöntemine benzer şekilde çalışır.
  • RIGHT_REDUCE, azaltık solun tersidir, yani SAĞ'da değerler biriktirir ve sol değişkeni değiştirmeye devam eder.

  • reduceLeftOption ve reduceRightOption, left_reduce ile benzerdir ve right_reduce tek farkı OPTION nesnesindeki sonuçları döndürmeleridir.

Aşağıda belirtilen kodun çıktısının bir kısmı: -

scansayılar listesi üzerinde işlem kullanma ( seeddeğer kullanma 0)List(-2,-1,0,1,2)

  • {0, -2} => - 2 {-2, -1} => - 3 {-3,0} => - 3 {-3,1} => - 2 {-2,2} => 0 tarama Listesi (0, -2, -3, -3, -2, 0)

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

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

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

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

kullanarak reduce, foldStrings bir liste üzerinde operasyonlarList("A","B","C","D","E")

  • {A, B} => AB {AB, C} => ABC {ABC, D} => ABCD {ABCD, E} => ABCDE azalması (a + b) ABCDE
  • {A, B} => AB {AB, C} => ABC {ABC, D} => ABCD {ABCD, E} => ABCDE reduceLeft (a + b) ABCDE
  • {A, B} => BA {BA, C} => CBA {CBA, D} => DCBA {DCBA, E} => EDCBA azaltma Sol (b + a) EDCB
  • {D, E} => DE {C, DE} => CDE {B, CDE} => BCDE {A, BCDE} => ABCDE reduceRight (a + b) ABCDE
  • {D, E} => ED {C, ED} => EDC {B, EDC} => EDCB {A, EDCB} => EDCBA reduceRight (b + a) EDCBA

Kod:

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
Bu yazı zar zor okunabilir. Lütfen cümleleri kısaltın, gerçek anahtar kelimeler kullanın (örneğin, LEFT_REDUCE yerine reduceLeft). Kodla uğraşırken gerçek matematiksel okları, kod etiketlerini kullanın. Her şeyi açıklamak yerine giriş / çıkış örneklerini tercih edin. Ara hesaplamalar okumayı zorlaştırır.
Mikaël Mayer

4

X0, x1, x2, x3 elemanlı x koleksiyonu ve keyfi fonksiyon f için aşağıdakilere sahipsiniz:

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

Sonuç olarak

  • scangibidir foldancak tüm ara değerleri de yayar
  • reduce bazen bulmak biraz daha zor olan bir başlangıç ​​değerine ihtiyaç duymaz
  • fold bulunması biraz daha zor olan bir başlangıç ​​değerine ihtiyaç duyar:
    • Toplamlar için 0
    • 1 ürünler için
    • min için ilk öğe (bazıları Integer.MAX_VALUE önerebilir)
  • % 100 emin değilim, ancak bu eşdeğer uygulamalar var gibi görünüyor:
    • 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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.