कम करें, मोड़ें या स्कैन करें (लेफ्ट / राइट)?


186

जब मैं का उपयोग करना चाहिए reduceLeft, reduceRight, foldLeft, foldRight, scanLeftया scanRight?

मैं उनके अंतर का अंतर्ज्ञान / अवलोकन चाहता हूं - संभवतः कुछ सरल उदाहरणों के साथ।


अनुशंसा करें कि आप stackoverflow.com/questions/25158780/…
samthebest

1
सूचक के लिए धन्यवाद। यह मेरे तकनीकी ज्ञान से थोड़ा ऊपर है :) क्या मेरे जवाब में कुछ ऐसा है जो आपको लगता है कि स्पष्ट किया जाना चाहिए / बदल दिया जाना चाहिए?
मार्क ग्रू

नहीं, बस थोड़ा सा इतिहास और एमपीपी की प्रासंगिकता की ओर इशारा करते हैं।
samthebest

अच्छी तरह से, के बीच के अंतर को कड़ाई से बोलना reduceऔर foldप्रारंभ मूल्य का अस्तित्व नहीं है - बल्कि यह एक अधिक गहन अंतर्निहित गणितीय कारण का परिणाम है।
samthebest

जवाबों:


370

सामान्य तौर पर, सभी 6 गुना फ़ंक्शन एक संग्रह के प्रत्येक तत्व के लिए एक बाइनरी ऑपरेटर लागू करते हैं। प्रत्येक चरण का परिणाम अगले चरण पर पारित किया जाता है (बाइनरी ऑपरेटर के दो तर्कों में से एक पर इनपुट के रूप में)। इस तरह से हम कर सकते हैं cumulate परिणामस्वरूप।

reduceLeftऔर reduceRightएक परिणाम को कम करना।

foldLeftऔर foldRightप्रारंभ मूल्य का उपयोग करके एकल परिणाम को कम करें।

scanLeftऔर scanRightएक शुरुआत मूल्य का उपयोग करके मध्यवर्ती संचयी परिणामों का एक संग्रह तैयार करना।

संचय करें

बाएँ और आगे से ...

तत्वों के एक संग्रह abcऔर एक बाइनरी ऑपरेटर के साथ addहम पता लगा सकते हैं कि संग्रह के LEFT तत्व से (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


राइट और पीछे की ओर से ...

यदि हम सही तत्व से शुरू करते हैं और पीछे की ओर जाते हैं (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)

डी-cumulate

बाएँ और आगे से ...

यदि इसके बजाय हम के लिए गए थे डी-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 विविधताओं के लिए बाहर देखो! याद रखें कि xRight भिन्नताओं में संचयी मान (de-) हमारे बाइनरी ऑपरेटर के दूसरे पैरामीटर को पास किया गया है :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) 

अंतिम सूची (-2, 3, -1, 4, 0) शायद वह नहीं है जो आप सहज रूप से उम्मीद करेंगे!

जैसा कि आप देख रहे हैं, आप जाँच सकते हैं कि आपका foldX क्या कर रहा है इसके बजाय बस एक scanX चला रहा है और प्रत्येक चरण में संचयी परिणाम को डीबग करें।

जमीनी स्तर

  • reduceLeftया के साथ एक परिणाम कम करें reduceRight
  • एक परिणाम के साथ foldLeftया foldRightयदि आपके पास प्रारंभ मूल्य है तो कम्युलेट करें ।
  • साथ मध्यवर्ती परिणाम का एक संग्रह cumulate scanLeftया scanRight

  • यदि आप संग्रह के माध्यम से आगे जाना चाहते हैं तो एक xLeft भिन्नता का उपयोग करें ।

  • यदि आप संग्रह के माध्यम से पीछे की ओर जाना चाहते हैं तो xRight भिन्नता का उपयोग करें ।

14
यदि मैं गलत नहीं हूं, तो बाएं संस्करण पूंछ कॉल अनुकूलन का उपयोग कर सकता है, जिसका अर्थ है कि यह बहुत अधिक कुशल है।
Trylks

3
@ मैर्क, मुझे पत्रों के साथ उदाहरण पसंद हैं, इसने चीजों को बहुत स्पष्ट कर दिया है
मुहम्मद फ़रग

@ ट्रायक्स फोल्डराइट को टेलरेक के साथ भी लागू किया जा सकता है
टिमोथी किम

@TimothyKim ऐसा करने के लिए अनुकूलित गैर-सीधे कार्यान्वयन के साथ कर सकता है। उदा। स्कैला सूचियों के विशेष मामले में , उस तरीके Listको फिर से लागू करने के लिए उलट दिया जाता है foldLeft। अन्य संग्रह विभिन्न रणनीतियों को लागू कर सकते हैं। सामान्य में, अगर foldLeftऔर foldRightinterchangeably उपयोग किया जा सकता है (लागू ऑपरेटर के साहचर्य संपत्ति), तो foldLeftअधिक कुशल और बेहतर है।
ट्राईलैक्स

9

आम तौर पर REDUCE, FOLD, SCAN विधि LEFT पर डेटा जमा करके काम करता है और RIGHT वैरिएबल को बदलता रहता है। उनके बीच मुख्य अंतर REDUCE है, फोल्ड है: -

गुना हमेशा एक seedमूल्य के साथ शुरू होगा यानी उपयोगकर्ता परिभाषित मूल्य। अगर संग्रह खाली है, तो अपवाद एक अपवाद फेंक देगा जहां तह बीज के मूल्य को वापस देता है। हमेशा एक ही मूल्य का परिणाम देगा।

स्कैन का उपयोग बाएं या दाएं हाथ से आइटम के कुछ प्रसंस्करण क्रम के लिए किया जाता है, फिर हम बाद की गणना में पिछले परिणाम का उपयोग कर सकते हैं। इसका मतलब है कि हम आइटम स्कैन कर सकते हैं। हमेशा एक संग्रह का परिणाम होगा।

  • LEFT_REDUCE विधि REDUCE विधि के समान काम करती है।
  • RIGHT_REDUCE किसी एक को कम करने के विपरीत है अर्थात यह RIGHT में मानों को जमा करता है और बाएं चर को बदलता रहता है।

  • कम करना और कम करना राइट ऑफ लेफ्ट_रेड और राईट_्रेड्यूस के समान है केवल यही अंतर है कि वे ओपियन ऑब्जेक्ट में परिणाम लौटाते हैं।

नीचे दिए गए कोड के लिए आउटपुट का एक हिस्सा होगा: -

scanसंख्याओं की सूची में ऑपरेशन का उपयोग करना ( seedमूल्य का उपयोग करके 0)List(-2,-1,0,1,2)

  • {, -2, = = - 2 {-2, -1} => - 3 {-3,0} => - 3 {-3,1} => - 2 {-2,2} => 0 स्कैन सूची (0, -2, -3, -3, -2, 0)

  • {, -2, = = - 2 {-2, -1} => - 3 {-3,0} => - 3 {-3,1} => - 2 {-2,2} => 0 ScanLeft (a + b) सूची (0, -2, -3, -3, -2, 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")

  • {{B, B} => AB {AB, C} => ABC {ABC, D} => ABCD {ABCD, E} => ABCDE कम (a + b) ABCDE
  • {{B, B} => AB {AB, C} => ABC {ABC, D} => ABCD {ABCD, E} => ABCDE घटाना (a + b) ABCDE
  • {, B, 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 कम करें (a + b) ABCDE
  • {D, E} => ED {C, ED} => EDC {B, EDC} => EDCB {A, EDCB} => EDCBA कम करें (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 के बजाय कम करें)। जब आप कोड के साथ काम कर रहे हों तो असली गणितीय तीर, कोड टैग का उपयोग करें। सब कुछ समझाने के बजाय इनपुट / आउटपुट उदाहरणों को प्राथमिकता दें। इंटरमीडिएट गणनाओं को पढ़ने में मुश्किल होती है।
मिकाइल मेयर

4

तत्वों x0, X1, x2, x3 और एक मनमाना फ़ंक्शन के साथ संग्रह x के लिए आपके पास निम्नलिखित हैं:

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
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.