क्या कोई हास्केल के संस्मरण के पीछे की अवधारणा को समझा सकता है?


12

(ध्यान दें कि मैं प्रश्न यहाँ डाल रहा हूँ क्योंकि यह वैचारिक यांत्रिकी के बारे में है, बजाय एक कोडिंग समस्या के)

मैं एक छोटे से कार्यक्रम पर काम कर रहा था, जो अपने समीकरण में फाइटवर्क नंबरों के अनुक्रम का उपयोग कर रहा था, लेकिन मैंने देखा कि अगर मुझे एक निश्चित संख्या में मिला है तो यह काफी धीमा हो गया है, एक बिट के चारों ओर घूमना, जिसे मैं हास्केल में एक तकनीक के रूप में जाना जाता है Memoization। उन्होंने इस तरह काम करते हुए कोड दिखाया:

-- Traditional implementation of fibonacci, hangs after about 30
slow_fib :: Int -> Integer
slow_fib 0 = 0
slow_fib 1 = 1
slow_fib n = slow_fib (n-2) + slow_fib (n-1)

-- Memorized variant is near instant even after 10000
memoized_fib :: Int -> Integer
memoized_fib = (map fib [0 ..] !!)
   where fib 0 = 0
         fib 1 = 1
         fib n = memoized_fib (n-2) + memoized_fib (n-1)

तो आप लोगों से मेरा सवाल यह है कि यह काम कैसे या नहीं होता है?

क्या यह इसलिए है क्योंकि यह किसी भी तरह से गणना पकड़ने से पहले अधिकांश सूची से गुजरने का प्रबंधन करता है? लेकिन अगर हेकेल आलसी है, तो वास्तव में कोई गणना नहीं है जिसे पकड़ने की जरूरत है ... तो यह कैसे काम करता है?


1
क्या आप स्पष्ट कर सकते हैं कि आपका क्या मतलब है the calculation catches up? BTW, संस्मरण haskell के लिए विशिष्ट नहीं है: en.wikipedia.org/wiki/Memoization
शमौन Bergot

किशन के जवाब के तहत मेरी व्याख्या देखें
इलेक्ट्रिक कॉफी

2
अपने प्रश्न से प्यार करो; बस एक त्वरित टिप्पणी: तकनीक को मेमो आई ज़ेशन कहा जाता है , न कि मेमो री ज़ेशन।
राचेत

जवाबों:


11

बस वास्तविक संस्मरण के पीछे यांत्रिकी की व्याख्या करने के लिए,

memo_fib = (map fib [1..] !!)

"थ्रक्स" की सूची तैयार करता है, अविकसित अभिकलन। जब तक हम उन्हें नहीं छूते हैं, तब तक उन्हें बिना सोचे-समझे प्रस्तुत करने के बारे में सोचें, तो वे नहीं चलेंगे।

अब एक बार जब हम एक थन का मूल्यांकन करते हैं, तो हम उसका फिर से मूल्यांकन नहीं करते हैं। यह वास्तव में "सामान्य" हैस्केल में उत्परिवर्तन का एकमात्र रूप है, एक बार ठोस मान बनने के लिए मूल्यांकन किया जाता है।

तो वापस अपने कोड में, आपको थ्रक्स की एक सूची मिल गई है, और आप अभी भी इस पेड़ की पुनरावृत्ति करते हैं, लेकिन आप सूची का उपयोग करके पुनरावृत्ति करते हैं, और एक बार सूची में एक तत्व का मूल्यांकन करने के बाद, यह फिर से गणना नहीं करता है। इस प्रकार, हम भोले रेशे के कार्य में पेड़ की पुनरावृत्ति से बचते हैं।

स्पर्शोन्मुख रूप से दिलचस्प नोट के रूप में, यह विशेष रूप से तेजी से फाइबोनैसी संख्याओं की एक श्रृंखला पर गणना की जाती है क्योंकि उस सूची का केवल एक बार मूल्यांकन किया जाता है, जिसका अर्थ है कि यदि आप memo_fib 10000दो बार गणना करते हैं , तो दूसरी बार तात्कालिक होना चाहिए। इसका कारण यह है कि हास्केल ने केवल एक बार कार्यों के लिए तर्कों का मूल्यांकन किया और आप लंबोदर के बजाय आंशिक अनुप्रयोग का उपयोग कर रहे हैं।

TLDR: किसी सूची में गणनाओं को संग्रहीत करके, सूची के प्रत्येक तत्व का मूल्यांकन एक बार किया जाता है, इसलिए, प्रत्येक फ़ाइबोनैचि संख्या की गणना पूरे कार्यक्रम के दौरान एक बार की जाती है।

दृश्य:

 [THUNK_1, THUNK_2, THUNK_3, THUNK_4, THUNK_5]
 -- Evaluating THUNK_5
 [THUNK_1, THUNK_2, THUNK_3, THUNK_4, THUNK_3 + THUNK_4]
 [THUNK_1, THUNK_2, THUNK_1 + THUNK_2, THUNK_4, THUNK_3 + THUNK_4]
 [1, 1, 1 + 1, THUNK_4, THUNK_3 + THUNK_4]
 [1, 1, 2, THUNK_4, 2 + THUNK4]
 [1, 1, 2, 1 + 2, 2 + THUNK_4]
 [1, 1, 2, 3, 2 + 3]
 [1, 1, 2, 3, 5]

इसलिए आप देख सकते हैं कि मूल्यांकन के THUNK_4लिए बहुत तेज है क्योंकि यह सबटेक्शंस पहले से ही मूल्यांकन किया गया है।


क्या आप इस बात का उदाहरण प्रदान कर सकते हैं कि सूची के मान थोड़े अनुक्रम के लिए कैसे व्यवहार करते हैं? मुझे लगता है कि यह काम करने के तरीके के दृश्य में जोड़ सकता है ... और जबकि यह सच है कि अगर मैं memo_fibएक ही मूल्य के साथ दो बार कॉल करता हूं , तो दूसरी बार तत्काल होगा, लेकिन अगर मैं इसे 1 उच्च मूल्य के साथ कहता हूं, तो अभी भी हमेशा के लिए मूल्यांकन करने के लिए लेता है (जैसे 30 से 31 तक जाने के लिए कहते हैं)
इलेक्ट्रिक कॉफ़ी

@ElectricCoff जोड़ा
डेनियल ग्रैज़र

@ElectricCfi नहीं, यह तब से नहीं होगा memo_fib 29और memo_fib 30पहले से ही मूल्यांकन किया जाता है, यह उन दो नंबरों को जोड़ने में जितना समय लगेगा उतना ही समय लगेगा :) एक बार जब कुछ eval-ed होता है, तो यह evaled रहता है।
डेनियल ग्रैज़र

1
@ElectricCfish आपकी पुनरावृत्ति को सूची से गुजरना होगा, अन्यथा आप किसी भी प्रदर्शन को हासिल नहीं करते हैं
डैनियल ग्रैज़र

2
@ElectricCfish हाँ। लेकिन सूची का 31 वां तत्व पिछली गणनाओं का उपयोग नहीं कर रहा है, आप हां को याद कर रहे हैं, लेकिन एक बहुत ही बेकार तरीके से .. गणना जो दोहराई जाती है, दो बार गणना नहीं की जाती है, लेकिन आपके पास अभी भी प्रत्येक नए मूल्य के लिए पेड़ की पुनरावृत्ति है बहुत, बहुत धीमा
डैनियल ग्रैज़र

1

संस्मरण की बात कभी भी एक ही फ़ंक्शन की दो बार गणना करने के लिए नहीं है - यह उन कम्प्यूटेशंस को गति देने के लिए बेहद उपयोगी है जो विशुद्ध रूप से कार्यात्मक हैं, अर्थात साइड इफेक्ट्स के बिना, क्योंकि उन लोगों के लिए प्रक्रिया को शुद्धता को प्रभावित किए बिना पूरी तरह से स्वचालित किया जा सकता है। यह उन कार्यों के लिए विशेष रूप से आवश्यक है fibo, जो पेड़ की पुनरावृत्ति की ओर ले जाते हैं , अर्थात घातीय प्रयास, जब इसे भोलेपन से लागू किया जाता है। (यह एक कारण है कि फाइबोनैचि संख्या वास्तव में पुनरावृत्ति सिखाने के लिए एक बहुत बुरा उदाहरण है - लगभग सभी डेमो कार्यान्वयन जो आप ट्यूटोरियल या पुस्तकों में पाते हैं, बड़े इनपुट मानों के लिए अनुपयोगी हैं।)

यदि आप निष्पादन के प्रवाह का पता लगाते हैं, तो आप देखेंगे कि दूसरे मामले में, निष्पादित fib xहोने पर हमेशा के लिए मान उपलब्ध होगा fib x+1, और रनटाइम सिस्टम केवल एक और पुनरावर्ती कॉल के माध्यम से मेमोरी से इसे पढ़ने में सक्षम होगा, जबकि पहला समाधान छोटे मूल्यों के लिए परिणाम उपलब्ध होने से पहले बड़े समाधान की गणना करने की कोशिश करता है। यह अंततः इसलिए है क्योंकि पुनरावृत्त [0..n]का मूल्यांकन बाएं से दाएं किया जाता है और इसलिए इसके साथ शुरू होगा 0, जबकि पहले उदाहरण में पुनरावृत्ति के साथ शुरू होता है nऔर उसके बाद ही इसके बारे में पूछता है n-1। यह वही है जो कई, कई अनावश्यक डुप्लिकेट फ़ंक्शन कॉल की ओर जाता है।


ओह, मैं इसके बारे में समझता हूं, मुझे समझ में नहीं आया कि यह कैसे काम करता है, जैसे कि मैं कोड में क्या देख सकता हूं, क्या यह है कि जब आप memorized_fib 20उदाहरण के लिए लिखते हैं , तो आप वास्तव में सिर्फ लिख रहे हैं map fib [0..] !! 20, फिर भी इसकी गणना करने की आवश्यकता होगी। 20 तक की पूरी श्रृंखला, या मैं यहाँ कुछ याद कर रहा हूँ?
इलेक्ट्रिक कॉफ़ी

1
हां, लेकिन प्रत्येक संख्या के लिए केवल एक बार। भोली कार्यान्वयन fib 2इतनी बार गणना करता है कि यह आपके सिर को स्पिन कर देगा - आगे बढ़ो, कॉल ट्री फर को लिखो जैसे कि एक छोटा मूल्य n==5। आप एक बार फिर से याद रखना कभी नहीं भूलेंगे क्योंकि आपने जो देखा है वह आपको बचाता है।
किलन फ़ॉथ

@ ईलेक्ट्रिक कॉफ़ी: हाँ, यह 1 से 20 के रेशे की गणना करेगा। आप उस कॉल से कुछ हासिल नहीं करते हैं। अब फ़ाइब 21 की गणना करने का प्रयास करें, और आप देखेंगे कि 1-21 की गणना के बजाय, आप केवल 21 की गणना कर सकते हैं क्योंकि आपके पास पहले से ही 1-20 की गणना है और इसे फिर से करने की आवश्यकता नहीं है।
फिशी

मैं कॉल ट्री के लिए लिखने की कोशिश कर रहा हूं n = 5, और मैं वर्तमान में उस बिंदु पर पहुंच गया हूं n == 3, जहां अब तक बहुत अच्छा है, लेकिन शायद यह सिर्फ मेरा अनिवार्य दिमाग है यह सोच रहा है, लेकिन इसका मतलब यह नहीं है कि इसके लिए n == 3, आपको बस मिल जाएगा map fib [0..]!!3? जो तब fib nकार्यक्रम की शाखा में जाता है ... जहां मुझे पूर्व-संकलित डेटा का लाभ मिलता है?
इलेक्ट्रिक कॉफ़ी

1
नहीं, memoized_fibठीक है। यह slow_fibहै कि तुम रो कर देगा अगर आप इसे का पता लगाने।
किलन फ़ॉथ
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.