ย่อพับหรือสแกน (ซ้าย / ขวา)


187

เมื่อฉันควรใช้reduceLeft, reduceRight, foldLeft, foldRight, scanLeftหรือscanRight?

ฉันต้องการสัญชาตญาณ / ภาพรวมของความแตกต่าง - อาจมีตัวอย่างง่ายๆ


แนะนำให้คุณดูstackoverflow.com/questions/25158780/…
samthebest

1
ขอบคุณสำหรับตัวชี้ มันเหนือความรู้ด้านเทคนิคเล็กน้อย :) มีคำตอบของฉันที่คุณคิดว่าควรจะชี้แจง / เปลี่ยนแปลงหรือไม่?
Marc Grue

ไม่เพียงแค่ชี้ให้เห็นประวัติและความเกี่ยวข้องกับ MPP
samthebest

ทีนี้พูดถึงความแตกต่างระหว่างreduceและfoldไม่ใช่การเริ่มต้นตามตัวอักษร - แต่นั่นเป็นผลมาจากเหตุผลทางคณิตศาสตร์ที่ลึกซึ้งกว่า
samthebest

คำตอบ:


370

โดยทั่วไปฟังก์ชั่นทั้ง 6 เท่าจะใช้ตัวดำเนินการไบนารี่กับแต่ละองค์ประกอบของคอลเลกชัน ผลลัพธ์ของแต่ละขั้นตอนจะถูกส่งไปยังขั้นตอนถัดไป (เป็นอินพุตไปยังหนึ่งในสองอาร์กิวเมนต์ของโอเปอเรเตอร์ไบนารี) ด้วยวิธีนี้เราสามารถสะสมผลลัพธ์

reduceLeftและreduceRightสะสมผลลัพธ์เดียว

foldLeftและfoldRightสะสมผลลัพธ์เดียวโดยใช้ค่าเริ่มต้น

scanLeftและscanRightสะสมชุดผลลัพธ์สะสมขั้นกลางโดยใช้ค่าเริ่มต้น

ซื้อสะสม

จากซ้ายไปข้างหน้า ...

ด้วยชุดขององค์ประกอบabcและตัวดำเนินการแบบไบนารีaddเราสามารถสำรวจฟังก์ชันการพับที่แตกต่างกันเมื่อทำต่อจากองค์ประกอบด้านซ้ายของชุดรวม (จาก A ถึง 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


จากขวาและย้อนกลับ ...

ถ้าเราเริ่มต้นด้วยองค์ประกอบ RIGHT และย้อนกลับ (จาก C ถึง A) เราจะสังเกตเห็นว่าตอนนี้อาร์กิวเมนต์ที่สองของตัวดำเนินการไบนารีของเราสะสมผลลัพธ์ (ตัวดำเนินการเหมือนกันเราเพิ่งเปลี่ยนชื่ออาร์กิวเมนต์เพื่อทำให้บทบาทชัดเจน ):

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-สะสม

จากซ้ายไปข้างหน้า ...

แต่ถ้าเราจะยกเลิกการ cumulateผลบางอย่างโดยเริ่มต้นจากการลบองค์ประกอบซ้ายของคอลเลกชันที่เราจะ cumulate ผลผ่านอาร์กิวเมนต์แรกresของผู้ประกอบการไบนารีของเรา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)


จากขวาและย้อนกลับ ...

แต่ระวังความแตกต่างของ xRight ทันที! โปรดจำไว้ว่าค่า (de-) ที่สะสมในรูปแบบ xRight จะถูกส่งไปยังพารามิเตอร์ที่สองresของตัวดำเนินการไบนารีของเรา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) 

รายการสุดท้าย (-2, 3, -1, 4, 0) อาจไม่ใช่สิ่งที่คุณคาดหวังอย่างสังหรณ์ใจ!

อย่างที่คุณเห็นคุณสามารถตรวจสอบสิ่งที่ foldX ของคุณกำลังทำอยู่เพียงแค่เรียกใช้ scanX แทนและตรวจแก้จุดบกพร่องผลลัพธ์ที่สะสมในแต่ละขั้นตอน

บรรทัดล่าง

  • สะสมผลด้วยหรือreduceLeftreduceRight
  • สะสมผลลัพธ์ด้วยfoldLeftหรือfoldRightถ้าคุณมีค่าเริ่มต้น
  • สะสมคอลเลกชันของผลกลางด้วยหรือscanLeftscanRight

  • ใช้รูปแบบ xLeft หากคุณต้องการส่งต่อไปยังคอลเล็กชัน

  • ใช้รูปแบบ xRight หากคุณต้องการย้อนกลับไปในคอลเล็กชัน

14
หากฉันไม่เข้าใจผิดเวอร์ชันด้านซ้ายสามารถใช้การปรับแต่งการโทรหางซึ่งหมายความว่ามีประสิทธิภาพมากขึ้น
Trylks

3
@ Marc ฉันชอบตัวอย่างที่มีตัวอักษรมันทำให้สิ่งต่าง ๆ ชัดเจนมาก
Muhammad Farag

@Trylks foldRight ยังสามารถนำไปใช้กับ tailrec
Timothy Kim

@ TimothyKim มันสามารถทำได้โดยการปรับใช้ที่ไม่ตรงไปตรงมาให้ทำเช่นนั้น เช่นในกรณีของรายการ Scalaวิธีที่ประกอบด้วยในการย้อนกลับไปจากนั้นให้ใช้List foldLeftคอลเลกชันอื่น ๆ อาจใช้กลยุทธ์ที่แตกต่างกัน โดยทั่วไปหากfoldLeftและfoldRightสามารถใช้แทนกันได้ (คุณสมบัติการเชื่อมโยงของตัวดำเนินการที่ใช้) นั้นfoldLeftจะมีประสิทธิภาพและเป็นที่นิยมมากกว่า
Trylks

9

โดยปกติวิธีลด, พับ, สแกนจะทำงานโดยการรวบรวมข้อมูลบนซ้ายและทำการเปลี่ยนแปลงตัวแปร RIGHT ต่อไป ความแตกต่างที่สำคัญระหว่างพวกเขาคือ REDUCE, FOLD คือ: -

พับจะเริ่มต้นด้วยseedค่าเช่นผู้ใช้กำหนดค่าเริ่มต้น ลดจะโยนข้อยกเว้นถ้าคอลเลกชันว่างเปล่าที่เป็นเท่าให้กลับค่าเมล็ด จะส่งผลให้ค่าเดียวเสมอ

การสแกนใช้สำหรับลำดับการประมวลผลบางรายการจากด้านซ้ายหรือด้านขวาจากนั้นเราสามารถใช้ผลลัพธ์ก่อนหน้าในการคำนวณครั้งต่อไป นั่นหมายความว่าเราสามารถสแกนรายการต่างๆ จะส่งผลให้เกิดการรวบรวม

  • วิธี LEFT_REDUCE ทำงานคล้ายกับวิธีการ REDUCE
  • RIGHT_REDUCE อยู่ฝั่งตรงข้ามเพื่อลดซ้ายหนึ่งคือมันจะเก็บค่าเป็น RIGHT และเปลี่ยนตัวแปรซ้ายไปเรื่อย ๆ

  • lessLeftOption และ reductionRightOption คล้ายกับ left_reduce และ right_reduce ความแตกต่างเพียงอย่างเดียวคือพวกเขากลับผลลัพธ์ในวัตถุตัวเลือก

ส่วนหนึ่งของการส่งออกสำหรับรหัสที่กล่าวถึงด้านล่างจะเป็น: -

ใช้scanการดำเนินการมากกว่ารายการหมายเลข (ใช้seedค่า0)List(-2,-1,0,1,2)

  • {0, -2} => - 2 {-2, -1} => - 3 {-3,0} => - 3 {-3,1} => - 2 {-2,2} => 0 รายการสแกน (0, -2, -3, -3, -2, 0)

  • {0, -2} => - 2 {-2, -1} => - 3 {-3,0} => - 3 {-3,1} => - 2 {-2,2} => 0 scanLeft (a + b) รายการ (0, -2, -3, -3, -2, 0)

  • {0, -2} => - 2 {-2, -1} => - 3 {-3,0} => - 3 {-3,1} => - 2 {-2,2} => 0 scanLeft (b + a) รายการ (0, -2, -3, -3, -2, 0)

  • {2,0} => 2 {1,2} => 3 {0,3} => 3 {-1,3} => 2 {-2,2} => 0 scanRight (a + b) รายการ ( 0, 2, 3, 3, 2, 0)

  • {2,0} => 2 {1,2} => 3 {0,3} => 3 {-1,3} => 2 {-2,2} => 0 scanRight (b + a) รายการ ( 0, 2, 3, 3, 2, 0)

โดยใช้reduce, foldการดำเนินงานในช่วงรายการสตริงList("A","B","C","D","E")

  • {A, B} => AB {AB, C} => ABC {ABC, D} => ABCD {ABCD, E} => ABCDE ลด (a + b) ABCDE
  • {A, B} => AB {AB, C} => ABC {ABC, D} => ABCD {ABCD, E} => ABCDE ลดระดับ (a + b) ABCDE
  • {A, B} => BA {BA, C} => CBA {CBA, D} => DCBA {DCBA, E} => EDCBA ลดระดับ (b + a) EDCB
  • {D, E} => DE {C, DE} => CDE {B, CDE} => BCDE {A, BCDE} => ABCDE reducRight (a + b) ABCDE
  • {D, E} => ED {C, ED} => EDC {B, EDC} => EDCB {A, EDCB} => EDCBA reducRight (b + a) EDCBA

รหัส:

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
โพสต์นี้อ่านง่าย โปรดย่อประโยคให้สั้นลงใช้คำหลักจริง (เช่นลดระดับแทน LEFT_REDUCE) ใช้ลูกศรทางคณิตศาสตร์จริงแท็กรหัสเมื่อคุณจัดการกับรหัส ต้องการตัวอย่างอินพุต / เอาต์พุตแทนที่จะอธิบายทุกอย่าง การคำนวณระดับกลางทำให้อ่านยาก
Mikaël Mayer

4

สำหรับคอลเลกชัน x ที่มีองค์ประกอบ x0, x1, x2, x3 และฟังก์ชั่นตามอำเภอใจ f คุณมีดังต่อไปนี้:

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

สรุปแล้ว

  • scanเป็นเหมือนfoldแต่ยังปล่อยค่ากลางทั้งหมด
  • reduce ไม่ต้องการค่าเริ่มต้นซึ่งบางครั้งก็ยากที่จะหา
  • fold ต้องการค่าเริ่มต้นที่หาได้ยากขึ้นเล็กน้อย:
    • 0 สำหรับผลรวม
    • 1 สำหรับผลิตภัณฑ์
    • องค์ประกอบแรกสำหรับนาที (บางคนอาจแนะนำ Integer.MAX_VALUE)
  • ไม่แน่ใจ 100% แต่ดูเหมือนว่ามีการใช้งานที่เทียบเท่าเหล่านี้:
    • 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.