तह बनाम तह (या तह) के निहितार्थ


155

सबसे पहले, रियल वर्ल्ड हास्केल , जिसे मैं पढ़ रहा हूं, कहता है कि कभी उपयोग न करें foldlऔर इसके बजाय उपयोग करें foldl'। इसलिए मुझे इस पर भरोसा है।

लेकिन मैं foldrबनाम जब उपयोग करने के लिए धुंधला हूँ foldl'। हालाँकि मैं इस बात की संरचना देख सकता हूँ कि वे मेरे सामने अलग तरीके से कैसे काम करते हैं, मैं यह समझने में बहुत मूर्ख हूँ कि "कौन बेहतर है।" मुझे लगता है कि यह मेरे लिए ऐसा लगता है जैसे कि वास्तव में इसका उपयोग नहीं किया जाना चाहिए, क्योंकि वे दोनों एक ही जवाब देते हैं (वे नहीं?)। वास्तव में, इस निर्माण के साथ मेरा पिछला अनुभव रूबी injectऔर क्लोजर का है reduce, जो "बाएं" और "दाएं" संस्करण नहीं लगते हैं। (साइड सवाल: वे किस संस्करण का उपयोग करते हैं?)

कोई भी अंतर्दृष्टि जो मेरे जैसे स्मार्ट-चुनौती वाले प्रकार की मदद कर सकती है, बहुत सराहना की जाएगी!

जवाबों:


174

foldr f x ysजहाँ ys = [y1,y2,...,yk]जैसा दिखता है उसके लिए पुनरावृत्ति

f y1 (f y2 (... (f yk x) ...))

जबकि पुनरावृत्ति foldl f x ysजैसा दिखता है

f (... (f (f x y1) y2) ...) yk

यहां एक महत्वपूर्ण अंतर यह है कि यदि परिणाम को f x yकेवल के मूल्य का उपयोग करके गणना की जा सकती है x, तो foldrपूरी सूची की जांच करने की आवश्यकता नहीं है। उदाहरण के लिए

foldr (&&) False (repeat False)

Falseजबकि लौटता है

foldl (&&) False (repeat False)

कभी समाप्त नहीं होता। (नोट: repeat Falseएक अनंत सूची बनाता है जहां हर तत्व है False।)

दूसरी ओर, foldl'पूंछ पुनरावर्ती और सख्त है। यदि आप जानते हैं कि आपको पूरी सूची को किसी भी तरह से ट्रेस करना होगा (उदाहरण के लिए, किसी सूची में संख्याओं को foldl'समेटना ), तो अधिक स्थान है (और शायद समय-) की तुलना में कुशल foldr


2
फोल्डर में यह f y1 थंक के रूप में मूल्यांकन करता है, इसलिए यह गलत रिटर्न देता है, हालांकि फोल्ड में, एफ को इसके किसी भी पैरामीटर के बारे में पता नहीं चल सकता है। हास्केल में, कोई फर्क नहीं पड़ता कि यह पूंछ की पुनरावृत्ति है या नहीं, यह दोनों थ्रो अतिप्रवाह का कारण बन सकता है, अर्थात थंक है बहुत बड़ा। फोल्ड 'फाँसी के फंदे को तुरंत कम कर सकता है।
सवाईर

25
भ्रम से बचने के लिए, ध्यान दें कि कोष्ठक मूल्यांकन के वास्तविक क्रम को नहीं दिखाते हैं। चूंकि हास्केल आलसी है, इसलिए बाहरी अभिव्यक्तियों का मूल्यांकन पहले किया जाएगा।
Lii

ग्रेटे उत्तर। मैं यह जोड़ना चाहूंगा कि यदि आप एक तह चाहते हैं जो सूची के माध्यम से भाग के रास्ते को रोक सकता है, तो आपको तह का उपयोग करना होगा; जब तक मैं गलत नहीं हूँ, बायाँ तह बंद नहीं किया जा सकता। (आप यह संकेत देते हैं जब आप कहते हैं "यदि आप जानते हैं ... आप ... पूरी सूची को पार कर लेंगे")। इसके अलावा, टाइपो "केवल मूल्य का उपयोग कर" को "केवल मूल्य का उपयोग करके" बदला जाना चाहिए। यानी "पर" शब्द हटा दें। (Stackoverflow मुझे 2 char परिवर्तन प्रस्तुत नहीं करने देगा!)।
लेक्वेर्विग

@Lqueryvg बाएं सिलवटों को रोकने के दो तरीके: 1. इसे दाएं गुना के साथ कोड करें (देखें fodlWhile); 2. इसे एक बाएं स्कैन ( scanl) में परिवर्तित करें और इसे last . takeWhile pउसी या उसी तरह रोकें । उह, और 3. उपयोग mapAccumL। :)
को नेस

1
@Desty क्योंकि यह प्रत्येक चरण पर अपने समग्र परिणाम के नए हिस्से का उत्पादन करता है - इसके विपरीत foldl, जो इसके समग्र परिणाम को इकट्ठा करता है और सभी कार्य समाप्त होने के बाद ही इसका उत्पादन करता है और प्रदर्शन करने के लिए और अधिक चरण नहीं हैं। तो उदाहरण के लिए foldl (flip (:)) [] [1..3] == [3,2,1], इसलिए scanl (flip(:)) [] [1..] = [[],[1],[2,1],[3,2,1],...]... IOW, foldl f z xs = last (scanl f z xs)और अनंत सूचियों का कोई अंतिम तत्व नहीं है (जो, ऊपर के उदाहरण में, स्वयं एक अनंत सूची होगी, INF से 1 तक)।
विल नेस


33

उनके शब्दार्थ भिन्न होते हैं इसलिए आप सिर्फ इंटरचेंज foldlऔर नहीं कर सकते foldr। एक तत्वों को बाईं ओर से, दूसरे को दाएं से मोड़ता है। इस तरह, ऑपरेटर एक अलग क्रम में लागू हो जाता है। यह सभी गैर-सहयोगी संचालन के लिए मायने रखता है, जैसे कि घटाव।

Haskell.org विषय पर एक दिलचस्प लेख है


उनके शब्दार्थ केवल एक तुच्छ तरीके से भिन्न होते हैं, जो व्यवहार में अर्थहीन है: प्रयुक्त फ़ंक्शन के तर्कों का क्रम। तो इंटरफ़ेस-वार वे अभी भी विनिमेय के रूप में गिनते हैं। वास्तविक अंतर यह है कि यह केवल अनुकूलन / कार्यान्वयन लगता है।
Evi1M4chine

2
@ Evi1M4chine मतभेदों में से कोई भी तुच्छ नहीं है। इसके विपरीत, वे पर्याप्त हैं (और, हाँ, व्यवहार में सार्थक)। वास्तव में, अगर मैं आज यह उत्तर लिखता तो यह इस अंतर को और भी अधिक महत्व देता।
कोनराड रुडोल्फ

23

शीघ्र ही, foldrबेहतर होता है जब संचायक फ़ंक्शन अपने दूसरे तर्क पर आलसी होता है। हास्केल विकी के स्टैक ओवरफ़्लो (इच्छित उद्देश्य) पर अधिक पढ़ें ।


18

सभी उपयोगों के 99% के लिए इस कारण foldl'को प्राथमिकता दी जाती foldlहै कि यह अधिकांश उपयोगों के लिए निरंतर स्थान पर चल सकता है।

फंक्शन लें sum = foldl['] (+) 0। जब foldl'उपयोग किया जाता है, तो राशि की तुरंत गणना की जाती है, इसलिए sumएक अनंत सूची में आवेदन करना हमेशा के लिए चलेगा, और सबसे अधिक संभावना है कि निरंतर स्थान में (यदि आप Intएस, Doubleएस, Floatएस जैसी चीजों का उपयोग कर रहे हैं , Integerतो निरंतर स्थान से अधिक का उपयोग करेंगे । संख्या से बड़ा हो जाता है maxBound :: Int)।

के साथ foldl, एक थंक बनाया जाता है (उत्तर प्राप्त करने के तरीके के एक नुस्खा की तरह, जिसे बाद में संग्रहीत किया जा सकता है, बजाय उत्तर को संग्रहीत करने के)। ये थ्रो बहुत अधिक जगह ले सकते हैं, और इस मामले में, थंक को स्टोर करने की तुलना में अभिव्यक्ति का मूल्यांकन करना बहुत बेहतर है (स्टैक ओवरफ्लो के लिए अग्रणी ... और आपको लीड करने के लिए ... ओह, कोई बात नहीं)

उम्मीद है की वो मदद करदे।


1
बड़ा अपवाद यह है कि यदि फ़ंक्शन foldlकुछ भी नहीं करता है, लेकिन इसके एक या अधिक तर्कों पर कंस्ट्रक्टर लागू करता है।
dfeuer

क्या foldlवास्तव में सबसे अच्छा विकल्प कब है? (अनंत सूचियों की तरह जब foldrगलत विकल्प, अनुकूलन-वार होता है?)
Evi1M4chine

@ Evi1M4chine यह सुनिश्चित नहीं करता है कि foldrअनंत सूचियों के लिए गलत विकल्प होने से आपका क्या मतलब है । वास्तव में, आप उपयोग नहीं करना चाहिए foldlया foldl'अनंत सूचियों के लिए। स्टैक ओवरफ्लो पर हास्केल विकि को
केविनऑर्र

14

वैसे, रूबी injectऔर क्लोजर reduceहैं foldl(या foldl1, आप किस संस्करण का उपयोग करते हैं, इसके आधार पर)। आमतौर पर, जब किसी भाषा में केवल एक ही रूप होता है, तो यह बाईं ओर होता है, जिसमें पायथन reduce, पर्ल का List::Util::reduce, C ++ का accumulate, C # का s शामिल होता है।Aggregate , स्मॉलटॉक का inject:into:, PHP का array_reduce, मैथमैटिका का Fold, आदि कॉमन लिस्प का reduceडिफॉल्ट लेफ्ट फोल्ड होता है लेकिन राइट फोल्ड का विकल्प है।


2
यह टिप्पणी सहायक है लेकिन मैं स्रोतों की सराहना करूंगा।
टाइटेनियमडेकॉय

आम लिस्प का आलसीपन reduceनहीं है, इसलिए यह foldl'बहुत सारे विचार यहां लागू नहीं होते हैं।
माइक्रो वायरस 1

मुझे लगता है कि आपका मतलब है foldl', क्योंकि वे सख्त भाषा हैं, नहीं? अन्यथा, इसका मतलब यह नहीं होगा कि उन सभी संस्करणों के कारण स्टैक ओवरफ्लो foldlहोता है?
Evi1M4chine

6

जैसा कि कोनराड बताते हैं, उनके शब्दार्थ अलग हैं। उनके पास एक ही प्रकार नहीं है:

ghci> :t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
ghci> :t foldl
foldl :: (a -> b -> a) -> a -> [b] -> a
ghci> 

उदाहरण के लिए, सूची संलग्न ऑपरेटर (++) के साथ लागू किया जा सकता foldrके रूप में

(++) = flip (foldr (:))

जबकि

(++) = flip (foldl (:))

आपको एक प्रकार की त्रुटि देगा।


उनका प्रकार एक ही है, बस चारों ओर स्विच किया गया है, जो अप्रासंगिक है। बुरा पी / एनपी समस्या को छोड़कर उनका इंटरफ़ेस और परिणाम समान हैं (पढ़ें: अनंत सूचियां)। ;) कार्यान्वयन के कारण अनुकूलन व्यवहार में एकमात्र अंतर है, जहां तक ​​मैं बता सकता हूं।
Evi1M4chine

@ Evi1M4chine यह गलत है, इस उदाहरण को देखें: foldl subtract 0 [1, 2, 3, 4]मूल्यांकन करता है -10, जबकि foldr subtract 0 [1, 2, 3, 4]मूल्यांकन करता है -2। जबकि foldlवास्तव में है । 0 - 1 - 2 - 3 - 4foldr4 - 3 - 2 - 1 - 0
krapht

@krapht, foldr (-) 0 [1, 2, 3, 4]है -2और foldl (-) 0 [1, 2, 3, 4]है -10। दूसरी ओर, subtractक्या आप उम्मीद कर सकते (से पीछे की ओर है subtract 10 14है 4तो), foldr subtract 0 [1, 2, 3, 4]है -10और foldl subtract 0 [1, 2, 3, 4]है 2(सकारात्मक)।
पियानोजम्स ऑक्ट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.