क्या यह किसी भी पुनरावर्ती प्रक्रिया को पूंछ-पुनरावृत्ति में बदलने का एक सामान्य तरीका है?


13

ऐसा लगता है कि मैंने किसी भी पुनरावर्ती प्रक्रिया को पूंछ-पुनरावृत्ति में बदलने का एक सामान्य तरीका खोज लिया है :

  1. एक अतिरिक्त "परिणाम" पैरामीटर के साथ एक सहायक उप-प्रक्रिया को परिभाषित करें।
  2. उस पैरामीटर के लिए प्रक्रिया के रिटर्न मान पर लागू किया जाएगा जो लागू करें।
  3. आरंभ करने के लिए इस सहायक प्रक्रिया को कॉल करें। "परिणाम" पैरामीटर के लिए प्रारंभिक मान पुनरावर्ती प्रक्रिया के निकास बिंदु के लिए मूल्य है, ताकि परिणामी पुनरावृत्ति प्रक्रिया शुरू होती है जहां से पुनरावर्ती प्रक्रिया सिकुड़ना शुरू होती है।

उदाहरण के लिए, यहाँ मूल पुनरावर्ती प्रक्रिया को परिवर्तित किया जाना है ( SICP व्यायाम 1.17 ):

(define (fast-multiply a b)
  (define (double num)
    (* num 2))
  (define (half num)
    (/ num 2))
  (cond ((= b 0) 0)
        ((even? b) (double (fast-multiply a (half b))))
        (else (+ (fast-multiply a (- b 1)) a))))

यहाँ परिवर्तित, पूंछ-पुनरावर्ती प्रक्रिया ( SICP व्यायाम 1.18 ) है:

(define (fast-multiply a b)
  (define (double n)
    (* n 2))
  (define (half n)
    (/ n 2))
  (define (multi-iter a b product)
    (cond ((= b 0) product)
          ((even? b) (multi-iter a (half b) (double product)))
          (else (multi-iter a (- b 1) (+ product a)))))
  (multi-iter a b 0))

क्या कोई इसे साबित या नापसंद कर सकता है?


1
पहले सोचा: यह सभी अकेले पुनरावर्ती कार्यों के लिए काम कर सकता है , लेकिन मुझे आश्चर्य होगा कि अगर यह उन कार्यों के लिए काम करता है जो कई पुनरावर्ती कॉल करते हैं, तो इसका मतलब यह होगा कि, उदाहरण के लिए, आप स्टैक की आवश्यकता के बिना क्विकर को लागू कर सकते हैं। अंतरिक्ष। (क्विकॉर्ट के मौजूदा कुशल कार्यान्वयन आम तौर पर स्टैक पर 1 पुनरावर्ती कॉल करते हैं, और अन्य पुनरावर्ती कॉल को टेल-कॉल में बदल देते हैं जो हो सकता है (मैन्युअल रूप से या स्वचालित रूप से) लूप में बदल जाता है।)O(logn)
j_random_haff

दूसरा विचार: b2 की शक्ति का चयन करना दर्शाता है कि शुरू productमें 0 पर सेट करना काफी सही नहीं है; लेकिन 1 में बदलने पर यह काम नहीं करता है जब bयह अजीब होता है। शायद आपको 2 अलग-अलग संचायक मापदंडों की आवश्यकता है?
j_random_hacker

3
आपने वास्तव में एक गैर पूंछ पुनरावर्ती परिभाषा के परिवर्तन को परिभाषित नहीं किया है, कुछ परिणाम पैरामीटर को जोड़ना और संचय के लिए इसका उपयोग करना बहुत अस्पष्ट है, और शायद ही कभी अधिक जटिल मामलों, जैसे पेड़ ट्रैवर्सल्स, जहां आपको दो पुनरावर्ती कॉल करने के लिए सामान्यीकृत करता है। "निरंतरता" का एक अधिक सटीक विचार हालांकि मौजूद है, जिसमें आप काम का हिस्सा हैं, और फिर एक "निरंतरता" फ़ंक्शन को लेने के लिए अनुमति देते हैं, एक पैरामीटर के रूप में प्राप्त करना जो आपने अब तक किया है। इसे कंटीन्यू पासिंग स्टाइल (cps) कहा जाता है, en.wikipedia.org/wiki/Continuation-passing_style देखें ।
एरियल

4
ये स्लाइड्स fsl.cs.illipedia.edu/images/d/d5/CS422-Fall-2006-13.pdf में cps परिवर्तन का वर्णन है, जिसमें आप कुछ मनमाना अभिव्यक्ति लेते हैं (संभवतः गैर पूंछ कॉल के साथ फ़ंक्शन परिभाषाओं के साथ) और केवल पूंछ कॉल के साथ इसे एक समतुल्य अभिव्यक्ति में बदलना।
एरियल

@j_random_hacker हाँ, मैं देख सकता हूं कि मेरी "रूपांतरित" प्रक्रिया वास्तव में गलत है ...
nalzok

जवाबों:


12

आपके एल्गोरिथ्म का आपका वर्णन इस बिंदु पर मूल्यांकन करने के लिए वास्तव में बहुत अस्पष्ट है। लेकिन, यहां कुछ बातों पर विचार करना होगा।

सीपीएस

वास्तव में, किसी भी कोड को एक रूप में बदलने का एक तरीका है जो केवल पूंछ-कॉल का उपयोग करता है। यह सीपीएस परिवर्तन है। सीपीएस ( कंटीन्यूएशन-पासिंग स्टाइल ) प्रत्येक फ़ंक्शन को एक निरंतरता देकर कोड व्यक्त करने का एक रूप है। एक निरंतरता एक सार धारणा है जो "बाकी के एक संकलन" का प्रतिनिधित्व करती है। सीपीएस फॉर्म में व्यक्त कोड में, निरंतरता को पुन: प्राप्त करने का प्राकृतिक तरीका एक फ़ंक्शन के रूप में है जो एक मूल्य को स्वीकार करता है। सीपीएस में, मान को वापस करने वाले फ़ंक्शन के बजाय, यह फ़ंक्शन द्वारा वर्तमान निरंतरता का प्रतिनिधित्व "रिटर्न" करने के लिए फ़ंक्शन को लागू करता है।

उदाहरण के लिए, निम्नलिखित फ़ंक्शन पर विचार करें:

(lambda (a b c d)
  (+ (- a b) (* c d)))

इसे सीपीएस में इस प्रकार व्यक्त किया जा सकता है:

(lambda (k a b c d)
  (- (lambda (v1)
       (* (lambda (v2)
            (+ k v1 v2))
          a b))
     c d))

यह बदसूरत है, और अक्सर धीमा है, लेकिन इसके कुछ फायदे हैं:

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

TCO

यह मुझे लगता है कि पूंछ-पुनरावृत्ति (या सामान्य रूप से टेल-कॉल) से संबंधित होने का एकमात्र कारण पूंछ-कॉल अनुकूलन (TCO) के प्रयोजनों के लिए है। इसलिए मुझे लगता है कि पूछने के लिए एक बेहतर प्रश्न है "क्या मेरा परिवर्तन उपज कोड है जो पूंछ-कॉल अनुकूलन है?"।

यदि हम एक बार फिर सीपीएस पर विचार करते हैं, तो इसकी एक विशेषता यह है कि सीपीएस में व्यक्त कोड में केवल टेल-कॉल होते हैं। चूंकि सब कुछ एक पूंछ-कॉल है, इसलिए हमें स्टैक पर एक रिटर्न-पॉइंट बचाने की आवश्यकता नहीं है। तो सीपीएस फॉर्म में सभी कोड टेल-कॉल को अनुकूलित करना चाहिए , है ना?

खैर, बिल्कुल नहीं। आप देखते हैं, जबकि ऐसा प्रतीत हो सकता है कि हमने स्टैक को समाप्त कर दिया है, हमने जो कुछ भी किया है वह केवल हमारे द्वारा प्रतिनिधित्व करने के तरीके को बदलना है। स्टैक अब बंद का हिस्सा है जो एक निरंतरता का प्रतिनिधित्व करता है। इसलिए CPS जादुई रूप से हमारे सभी कोड टेल-कॉल को अनुकूलित नहीं बनाता है।

इसलिए यदि CPS सब कुछ TCO नहीं कर सकता है, तो क्या विशेष रूप से प्रत्यक्ष पुनरावृत्ति के लिए एक परिवर्तन है? नहीं, सामान्य तौर पर नहीं। कुछ पुनरावर्ती रेखीय हैं, लेकिन कुछ नहीं हैं। गैर-रैखिक (जैसे, पेड़) की पुनरावृत्ति बस कहीं न कहीं राज्य की एक चर राशि बनाए रखना चाहिए


" TCO " उप-धारा में होने पर यह थोड़ा भ्रमित करने वाला होता है , जब आप कहते हैं कि "टेल-कॉल ऑप्टिमाइज़्ड" का अर्थ है "आप वास्तव में निरंतर मेमोरी के साथ"। डायनेमिक मेमोरी का उपयोग स्थिर नहीं है, फिर भी इस तथ्य को नकारा नहीं जाता है कि कॉल वास्तव में टेल हैं और स्टैक के उपयोग में कोई वृद्धि नहीं हुई है । SICP ऐसी गणनाओं को "पुनरावृत्‍ती" कहता है, इसलिए "हालांकि यह TCO है, फिर भी यह इसे पुनरावृत्‍ति नहीं बनाता है" (मेरे लिए) बेहतर शब्द हो सकता है।
विल नेस

@WillNess हमारे पास अभी भी एक कॉल स्टैक है, यह सिर्फ अलग तरह से प्रस्तुत किया गया है। संरचना केवल इसलिए नहीं बदलती है क्योंकि हम हार्डवेयर स्टैक के बजाय ढेर का उपयोग कर रहे हैं । आखिरकार, गतिशील ढेर स्मृति के आधार पर बहुत सारी डेटा संरचनाएं हैं जिनके नाम में "स्टैक" है।
नाथन डेविस

यहाँ केवल यही कहा जाता है कि कुछ भाषाओं में कॉल स्टैक का उपयोग करने के लिए कठोर सीमाएँ हैं।
विल नेस
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.