आप कैसे जानते हैं कि फोल्ड-लेफ्ट का उपयोग कब करना है और फोल्ड-राइट का उपयोग कब करना है?


99

मुझे पता है कि गुना-बाएँ बाएँ-झुकाव वाले पेड़ पैदा करते हैं और गुना-दाएँ दाएँ-झुकाव वाले पेड़ पैदा करते हैं, लेकिन जब मैं एक मोड़ के लिए पहुँचता हूँ, तो मैं कभी-कभी खुद को सिरदर्द-उत्पीड़न में फंसता हुआ पाता हूँ कि किस तरह के गुना को निर्धारित करने की कोशिश कर रहा हूँ। उचित है। मैं आमतौर पर पूरी समस्या को समाप्त करता हूं और गुना फ़ंक्शन के कार्यान्वयन के माध्यम से कदम बढ़ाता हूं क्योंकि यह मेरी समस्या पर लागू होता है।

इसलिए मैं जानना चाहता हूं:

  • यह निर्धारित करने के लिए अंगूठे के कुछ नियम हैं कि क्या बाएं या दाएं को मोड़ना है?
  • मैं जल्दी से यह तय कर सकता हूं कि जिस समस्या का मैं सामना कर रहा हूं, उसे किस प्रकार का उपयोग करना है?

वहाँ में एक उदाहरण है द्वारा उदाहरण स्काला एक समारोह में कहा जाता समतल जो एक एकल सूची में तत्व सूचियों की एक सूची concatenates लिखने के लिए एक गुना का उपयोग कर के (पीडीएफ)। उस मामले में, एक सही तह उचित विकल्प है (जिस तरह से सूचियों को संक्षिप्त किया गया है उसे देखते हुए), लेकिन मुझे उस निष्कर्ष पर पहुंचने के लिए थोड़ा सोचना पड़ा।

चूंकि फोल्डिंग (कार्यात्मक) प्रोग्रामिंग में इस तरह की एक सामान्य क्रिया है, मैं इस प्रकार के निर्णय जल्दी और आत्मविश्वास से करने में सक्षम होना चाहता हूं। तो ... कोई सुझाव?



1
यह क्यू उस से अधिक सामान्य है, जो विशेष रूप से हास्केल के बारे में था। प्रश्न के उत्तर में आलस्य एक बड़ा अंतर बनाता है।
क्रिस कॉनवे

ओह। अजीब, किसी तरह मैंने सोचा कि मैंने इस सवाल पर एक हास्केल टैग देखा है, लेकिन मुझे नहीं लगता ...
भावार्थ

जवाबों:


105

आप एक इनफ़िक्स ऑपरेटर नोटेशन में गुना ट्रांसफर कर सकते हैं (बीच में लिखना):

यह उदाहरण संचायक फ़ंक्शन का उपयोग करके गुना करता है x

fold x [A, B, C, D]

इस प्रकार बराबरी करता है

A x B x C x D

अब आपको बस अपने ऑपरेटर की सहानुभूति (कोष्ठक लगाकर!) के बारे में जानना होगा।

यदि आपके पास एक बाएं-सहयोगी ऑपरेटर है, तो आप कोष्ठक को इस तरह सेट करेंगे

((A x B) x C) x D

यहां, आप बाईं ओर का उपयोग करते हैं । उदाहरण (हैसेल-स्टाइल स्यूडोकोड)

foldl (-) [1, 2, 3] == (1 - 2) - 3 == 1 - 2 - 3 // - is left-associative

यदि आपका ऑपरेटर राइट-एसोसिएटिव ( दाएं गुना ) है, तो कोष्ठक इस तरह सेट किए जाएंगे:

A x (B x (C x D))

उदाहरण: विपक्ष-संचालक

foldr (:) [] [1, 2, 3] == 1 : (2 : (3 : [])) == 1 : 2 : 3 : [] == [1, 2, 3]

सामान्य तौर पर, अंकगणित ऑपरेटर (अधिकांश ऑपरेटर) बाएं-सहयोगी होते हैं, इसलिए foldlअधिक व्यापक होता है। लेकिन अन्य मामलों में, infix अंकन + कोष्ठक काफी उपयोगी है।


6
ठीक है, आपने जो वर्णन किया है वह वास्तव में foldl1और foldr1हास्केल में है ( foldlऔर foldrएक बाहरी प्रारंभिक मूल्य लेते हैं), और हास्केल के "विपक्ष" को (:)नहीं कहा जाता है (::), लेकिन अन्यथा यह सही है। आप को जोड़ने के लिए है कि हास्केल इसके अलावा एक प्रदान करता है चाहते हो सकता है foldl'/ foldl1'जो की सख्त वेरिएंट हैं foldl/ foldl1क्योंकि आलसी गणित हमेशा वांछनीय नहीं है।
ईपीएम

क्षमा करें, मुझे लगा कि मैंने इस प्रश्न पर "हास्केल" टैग देखा है, लेकिन यह वहां नहीं है। मेरी टिप्पणी वास्तव में यह बहुत मायने नहीं रखती है अगर यह हास्केल नहीं है ...
भावुक

@ephemient आपने इसे देखा। यह "हैसेल-स्टाइल स्यूडोकोड" है। :)
हंसते

सर्वश्रेष्ठ उत्तर मैंने कभी भी सिलवटों के बीच अंतर से संबंधित देखा है।
एलेक्साउंडओएस

60

ओलिन शॉवर्स ने उन्हें यह कहते हुए विभेदित किया कि "तह मौलिक सूची पुनरावृति है" और "तहखाना मौलिक सूची पर्यवेक्षक है।" यदि आप देखें कि कैसे काम करता है:

((1 + 2) + 3) + 4

आप संचयकर्ता को देख सकते हैं (जैसा कि एक पूंछ-पुनरावर्ती पुनरावृत्ति में) बनाया जा रहा है। इसके विपरीत, गुना आय:

1 + (2 + (3 + 4))

जहां आप बेस केस 4 को ट्रावेलर देख सकते हैं और वहां से परिणाम तैयार कर सकते हैं।

इसलिए मैं अंगूठे का एक नियम प्रस्तुत करता हूं: यदि यह सूची पुनरावृत्ति जैसा दिखता है, तो वह जो पूंछ-पुनरावर्ती रूप में लिखना सरल होगा, तह जाने का रास्ता है।

लेकिन वास्तव में यह संभवतः उन ऑपरेटरों की सहानुभूति से स्पष्ट होगा जो आप उपयोग कर रहे हैं। यदि वे बाएं-सहयोगी हैं, तो फोल्ड का उपयोग करें। यदि वे सही-सहयोगी हैं, तो फोल्ड का उपयोग करें।


29

अन्य पोस्टरों ने अच्छे उत्तर दिए हैं और मैं पहले से कही गई बातों को नहीं दोहराऊंगा। जैसा कि आपने अपने प्रश्न में एक स्काला उदाहरण दिया है, मैं एक स्काला विशिष्ट उदाहरण दूंगा। जैसा कि ट्रिक्स ने पहले ही कहा था, स्टैक फ्रेम foldRightको संरक्षित करने की आवश्यकता है n-1, जहां nआपकी सूची की लंबाई है और इससे आसानी से स्टैक ओवरफ्लो हो सकता है - और पूंछ पुनरावृत्ति भी आपको इससे नहीं बचा सकती है।

A List(1,2,3).foldRight(0)(_ + _)कम करेगा:

1 + List(2,3).foldRight(0)(_ + _)        // first stack frame
    2 + List(3).foldRight(0)(_ + _)      // second stack frame
        3 + 0                            // third stack frame 
// (I don't remember if the JVM allocates space 
// on the stack for the third frame as well)

जबकि List(1,2,3).foldLeft(0)(_ + _)कम होगा:

(((0 + 1) + 2) + 3)

जैसा कि कार्यान्वितList किया जा सकता है, जैसा कि इसे लागू किया जा सकता है

स्कैला के रूप में एक कड़ाई से मूल्यांकन की गई भाषा में, foldRightबड़ी सूची के लिए स्टैक को आसानी से उड़ा सकता है, जबकि ऐसा foldLeftनहीं हो सकता।

उदाहरण:

scala> List.range(1, 10000).foldLeft(0)(_ + _)
res1: Int = 49995000

scala> List.range(1, 10000).foldRight(0)(_ + _)
java.lang.StackOverflowError
        at scala.List.foldRight(List.scala:1081)
        at scala.List.foldRight(List.scala:1081)
        at scala.List.foldRight(List.scala:1081)
        at scala.List.foldRight(List.scala:1081)
        at scala.List.foldRight(List.scala:1081)
        at scala.List.foldRight(List.scala:1081)
        at scala.List.foldRight(List.scala:1081)
        at scala.List.foldRight(List.scala:1081)
        at scala.List.foldRig...

मेरे अंगूठे का नियम इसलिए है - ऐसे ऑपरेटरों के लिए जिनके पास एक विशिष्ट संघातकता नहीं है, हमेशा उपयोग करें foldLeft, कम से कम स्काला में। अन्यथा, जवाब में दी गई अन्य सलाह के साथ जाएं;)।


13
यह पहले सच था, लेकिन स्काला के वर्तमान संस्करणों में, फोल्डराइट को सूची की उलटी हुई कॉपी पर फोल्डेफ्ट लागू करने के लिए बदल दिया गया है। उदाहरण के लिए, 2.10.3 में, github.com/scala/scala/blob/v2.10.3/src/library/scala/… । इस परिवर्तन की शुरुआत 2013 में हुई थी - github.com/scala/scala/commit/…
ध्रुव कपूर

4

यह भी ध्यान देने योग्य है (और मुझे लगता है कि यह स्पष्ट रूप से थोड़ा सा बता रहा है), एक कम्यूटेटिव ऑपरेटर के मामले में दोनों बहुत अधिक समान हैं। इस स्थिति में एक गुना बेहतर विकल्प हो सकता है:

तह: (((1 + 2) + 3) + 4)प्रत्येक ऑपरेशन की गणना कर सकता है और संचित मूल्य को आगे बढ़ा सकता है

तह: (1 + (2 + (3 + 4)))एक स्टैक फ्रेम की जरूरत है 1 + ?और 2 + ?गणना करने से पहले खोलने के लिए 3 + 4, फिर इसे वापस जाने और प्रत्येक के लिए गणना करने की आवश्यकता है।

मैं यह कहने के लिए कार्यात्मक भाषाओं या संकलक अनुकूलन पर एक विशेषज्ञ के लिए पर्याप्त नहीं हूं कि क्या यह वास्तव में एक फर्क पड़ेगा लेकिन यह निश्चित रूप से सराहनीय ऑपरेटरों के साथ एक मोड़ का उपयोग करने के लिए क्लीनर लगता है।


1
अतिरिक्त स्टैक फ़्रेम निश्चित रूप से बड़ी सूचियों के लिए एक अंतर बनाएंगे। यदि आपका स्टैक फ्रेम प्रोसेसर के कैश के आकार से अधिक है, तो आपके कैश-मिस प्रदर्शन को प्रभावित करने वाले हैं। जब तक सूची दोगुनी-कड़ी नहीं होती, तब तक तह को पूंछ-पुनरावर्ती कार्य करना कठिन होता है, इसलिए आपको तब तक तह का उपयोग करना चाहिए जब तक कि कोई कारण न हो।
ए। लेवी

4
हास्केल की आलसी प्रकृति इस विश्लेषण को मढ़ देती है। यदि फोल्ड किया जा रहा फ़ंक्शन दूसरे पैरामीटर में गैर-सख्त है, तो foldrबहुत अच्छी तरह से अधिक कुशल हो सकता है foldl, और किसी भी अतिरिक्त स्टैक फ़्रेम की आवश्यकता नहीं होगी।
एफ़ेमिएंट

2
क्षमा करें, मुझे लगा कि मैंने इस प्रश्न पर "हास्केल" टैग देखा है, लेकिन यह वहां नहीं है। मेरी टिप्पणी वास्तव में यह बहुत मायने नहीं रखती है अगर यह हास्केल नहीं है ...
भावुक
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.