2011-08-16 12 views
5

मैंने एक ऐसा फ़ंक्शन बनाया है जो आईडी जोड़े की सूचियों को पढ़ता है (यानी [("ए", "बी"), ("बी", "सी"), ("सी", "डी "), ...] और किसी भी शाखाओं सहित आईडी को शुरू करने से अनुक्रमित करता है।पायथन रिकर्सिव फ़ंक्शन रिकर्सन सीमा से अधिक है। मैं इसे पुनरावृत्ति में कैसे बदल सकता हूं

आदेशित आईडी की प्रत्येक सूची एक संरेखण नामक कक्षा में आयोजित की जाती है और यह फ़ंक्शन एक नया संरेखण शुरू करके शाखाओं को संभालने के लिए रिकर्सन का उपयोग करता है आईडी पर जिस पर शाखा मुख्य सूची से विभाजित होती है।

मुझे पता चला है कि कुछ इनपुट के साथ पायथन द्वारा निर्धारित अधिकतम रिकर्सन सीमा तक पहुंचना संभव है। मुझे पता है कि मैं sys.setrecursionlimit का उपयोग करके इस सीमा को बढ़ा सकता हूं (), लेकिन जैसा कि मुझे नहीं पता कि शाखाओं के कितने संयोजन संभव हैं, मैं इस रणनीति से बचना चाहता हूं।

मैं पुनरावर्ती कार्यों को पुनरावर्ती कार्यों में परिवर्तित करने के बारे में कई लेख पढ़ रहा हूं, लेकिन मैं इस विशेष कार्य को संभालने का सबसे अच्छा तरीका निर्धारित करने में सक्षम नहीं हूं क्योंकि समारोह के बीच में रिकर्सन होता है और घातीय हो सकता है।

क्या आप में से कोई भी सुझाव दे सकता है?

धन्यवाद, ब्रायन

कोड नीचे पोस्ट किया जाता है:

def buildAlignments(alignment, alignmentList, endIDs): 
    while alignment.start in endIDs: 

     #If endID only has one preceding ID: add preceding ID to alignment 
     if len(endIDs[alignment.start]) == 1: 
      alignment.add(endIDs[alignment.start][0]) 

     else: 

      #List to hold all branches that end at spanEnd 
      branches = [] 

      for each in endIDs[alignment.start]: 

       #New alignment for each branch 
       al = Alignment(each) 

       #Recursively process each new alignment 
       buildAlignments(al, branches, endIDs) 

       branches.append(al) 
      count = len(branches) 
      i = 0 
      index = 0 

      #Loop through branches by length 
      for branch in branches: 
       if i < count - 1: 

        #Create copy of original alignment and add branch to alignment 
        al = Alignment(alignment) 
        al += branch #branches[index] 
        alignmentList.append(al) 
        i += 1 

       #Add single branch to existing original alignment 
       else: alignment += branch #branches[index] 
       index += 1 

def main(): 
    IDs = [("L", "G"), ("A", "B"), ("B", "I"), ("B", "H"), ("B", "C"), ("F", "G"), ("D", "E"), ("D", "J"), ("E", "L"), ("C", "D"), ("E", "F"), ("J", "K")] 

    #Gather all startIDs with corresponding endIDs and vice versa 
    startIDs = {} 
    endIDs = {} 
    for pair in IDs: 
     if not pair[0] in startIDs: startIDs[pair[0]] = [] 
     startIDs[pair[0]].append(pair[1]) 
     if not pair[1] in endIDs: endIDs[pair[1]] = [] 
     endIDs[pair[1]].append(pair[0]) 

    #Create Alignment objects from any endID that does not start another pair (i.e. final ID in sequence) 
    alignments = [Alignment(end) for end in endIDs if not end in startIDs] 

    #Build build sequences in each original Alignment 
    i = len(alignments) 
    while i: 
     buildAlignments(alignments[i-1], alignments, endIDs) 
     i -= 1 

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

समाधान: एंड्रयू कुक के लिए धन्यवाद। नई विधि कॉल स्टैक पर बहुत आसान और बहुत आसान लगती है। मैंने अपने उद्देश्यों के अनुरूप बेहतर तरीके से अपने कोड में कुछ मामूली समायोजन किए हैं। शुरू से ही सूची जोड़ा if line in known: known.remove(line) क्रम में ही पूरी श्रृंखला बदली हुई लाइन स्ट्रिंग से चर बनाए रखने के लिए विस्तार करने के लिए समाप्त करने के लिए बनाने के लिए बदली लिंक और have_successors: परिवर्तन की

from collections import defaultdict 

def expand(line, have_successors, known): 
    #print line 
    known.append(line) 
    for child in have_successors[line[-1]]: 
     newline = line + [child] 
     if line in known: known.remove(line) 
     yield expand(newline, have_successors, known) 

def trampoline(generator): 
    stack = [generator] 
    while stack: 
     try: 
      generator = stack.pop() 
      child = next(generator) 
      stack.append(generator) 
      stack.append(child) 
     except StopIteration: 
      pass 

def main(pairs): 
    have_successors = defaultdict(lambda: set()) 
    links = set() 
    for (start, end) in pairs: 
     links.add(end) 
     have_successors[start].add(end) 
    known = [] 
    for node in set(have_successors.keys()): 
     if node not in links: 
      trampoline(expand([node], have_successors, known)) 
    for line in known: 
     print line 

if __name__ == '__main__': 
    main([("L", "G"), ("A", "B"), ("B", "I"), ("B", "H"), ("B", "C"), ("F", "G"), ("D", "E"), ("D", "J"), ("E", "L"), ("C", "D"), ("E", "F"), ("J", "K")]) 

सारांश: मैं नीचे पूरा समाधान को शामिल किया है एक आईडी में एकाधिक वर्णों को संभालने के लिए सूची में।

अद्यतन: तो मैंने अभी पाया है कि मुझे यह सब कुछ पता था क्योंकि मुझे यह उपलब्ध कराई गई आईडी की सूची में परिपत्र संदर्भों के लिए किया गया है। अब जब परिपत्र संदर्भ तय किया गया है, तो विधि अपेक्षा के अनुसार काम करता है। - आपके सहयोग के लिए पुन: धन्यवाद।

+0

मुझे लगता है कि आपका मतलब ऑफशूट है। –

+0

क्या यह आपका वास्तविक कोड है? यह इंडेक्स एरर को 'i = len (alignments)' पर फेंकता है ... 'बिल्ड एलाइनमेंट्स (संरेखण [i]' इससे पहले कि यह भी रिकर्सिंग शुरू हो जाए। –

+0

@Erin - इसे इंगित करने के लिए धन्यवाद। मैंने ऑफ-च्यूट्स को शाखाओं में बदल दिया है एक बेहतर विवरण। – Brian

उत्तर

14

आपका कोड एक असंगठित गड़बड़ है। मैं यह नहीं बता सकता कि इसे विस्तार से क्या करना है। यदि आप अधिक सावधान (neater, स्पष्ट) थे तो आप शायद रिफैक्टर को आसान भी पाएंगे। पिछले संस्करणों के साथ foo.__next__() या इसी तरह के next(foo) बदलना पड़ सकता है -

from collections import defaultdict 

def expand(line, links, known): 
    print 'expand' 
    known.append(line) 
    for child in links[line[-1]]: 
     newline = line + child 
     yield expand(newline, links, known) 

def trampoline(generator): 
    stack = [generator] 
    while stack: 
     try: 
      generator = stack.pop() 
      print 'next' 
      child = next(generator) 
      stack.append(generator) 
      stack.append(child) 
     except StopIteration: 
      pass 

def main(pairs): 
    have_successors = set() 
    links = defaultdict(lambda: set()) 
    for (start, end) in pairs: 
     have_successors.add(start) 
     links[end].add(start) 
    known = [] 
    for node in set(links.keys()): 
     if node not in have_successors: 
      trampoline(expand(node, links, known)) 
    for line in known: 
     print line 

if __name__ == '__main__': 
    main([("L", "G"), ("A", "B"), ("B", "I"), ("B", "H"), ("B", "C"), ("F", "G"), ("D", "E"), ("D", "J"), ("E", "L"), ("C", "D"), ("E", "F"), ("J", "K")]) 

मैं python2.7 प्रयोग किया है:

वैसे भी, आप यही चाहते की तरह कुछ कर सकते हैं।


क्लीनर कोड

पहले लिखने पर, मैं भी, एक आत्म सिखाया प्रोग्रामर जो एक शैक्षणिक (एक खगोल विज्ञानी) के रूप में शुरू कर रहा हूँ ताकि आप मेरी सहानुभूति है। और यदि आप आगे बढ़ते रहते हैं, तो आप कई "सिखाए गए" प्रोग्रामर को पकड़ सकते हैं और पास कर सकते हैं। यह उतना कठिन नहीं है जितना आप सोच सकते हैं ...

दूसरा डिफॉल्टडिक्ट जैसे "चाल" का उपयोग करने के बीच एक अंतर है, जो केवल अनुभव/अभ्यास का मामला है, और "साफ" है। मुझे उम्मीद है कि आप डिफॉल्टडिक्ट के बारे में जान लेंगे - जो समय के साथ आएगा।

लेकिन क्या आप अबऐसा करने में सक्षम होना चाहिए साफ, सरल कोड लिखने है:

  • मुझे लगता है कि आप टिप्पणी है कि कोड के पुराने संस्करणों के बारे में कर रहे हैं है। एक "अधिकतम लंबाई" का उल्लेख करता है लेकिन मुझे कोई लंबाई गणना नहीं दिखाई देती है। इसलिए या तो टिप्पणी पुरानी है (इस मामले में यह क्यों है) या इसे स्पष्ट होना चाहिए (अधिकतम लंबाई की चीजें क्यों हैं?)। आम तौर पर, आपको जितना संभव हो उतना टिप्पणी करना चाहिए, क्योंकि अन्यथा यह पुराना हो जाता है। लेकिन साथ ही आपको टिप्पणियों का उपयोग करना चाहिए जहां यह स्पष्ट नहीं है कि कोड के पीछे "विचार" क्या हैं। कोड स्वयं के लिए बोलना चाहिए, इसलिए यह न कहें कि "मैं यहां दो नंबर जोड़ रहा हूं", लेकिन कहें कि "यहां टुकड़े अधिकतम लंबाई होनी चाहिए क्योंकि ..." अगर कुछ "छुपा" तर्क है।

  • आपके द्वारा उपयोग की जाने वाली तस्वीरों से सावधान रहें। किसी कारण से आपका कोड ज्ञात टर्मिनल से शुरू होता है। तो आप अंत में चीजों को शुरुआत की तरफ से बना रहे हैं। क्यूं कर? यह समस्या को देखने का एक अजीब तरीका है। शुरुआत में होने वाले बिंदुओं से शुरू करना स्पष्ट नहीं होगा, लेकिन अंत में नहीं? और फिर उन्हें विकसित करने के लिए "startIDs" का उपयोग करें? इस तरह आप "आगे चलना" कर रहे हैं। यह एक छोटी सी चीज़ की तरह प्रतीत हो सकता है, लेकिन यह कोड को भ्रमित करने में पढ़ता है।

  • नौकरी के लिए सही उपकरण का उपयोग करें। आपने startIDs का उपयोग नहीं किया है, तो आप मानचित्र क्यों बना रहे हैं? आपको बस एक सेट था। शायद आपको सेट के बारे में पता नहीं था, इस मामले में ठीक है (लेकिन अब आप करते हैं!: ओ)। लेकिन अन्यथा, वह भी भ्रमित है - आपके कोड को पढ़ने वाला कोई व्यक्ति किसी कारण से काम करने की अपेक्षा करता है। इसलिए जब आप जरूरी से ज्यादा करते हैं तो वे आश्चर्य करते हैं कि क्यों।

  • जब आपको आवश्यकता नहीं है तो चीजों की गिनती से बचें। आपके पास i और index और count है। क्या वे सभी की जरूरत है? इन प्रकार के काउंटर बग रखने का सबसे आसान तरीका हैं, क्योंकि उनमें मूर्खतापूर्ण छोटी तर्क त्रुटियां हो सकती हैं। और वे कोड अस्पष्ट बनाते हैं। if i < count - 1: वास्तव में कह रहा है "क्या यह आखिरी शाखा है"? यदि ऐसा है, तो if branch == branches [-1]: लिखना बहुत अच्छा होगा क्योंकि यह है जो आप सोच रहे हैं के बारे में स्पष्ट है।

  • इसी तरह मुख्य रूप से संरेखण पर लूपिंग के साथ। i का उपयोग करके चीजों को जटिल बनाते हैं। आप प्रत्येक संरेखण को संसाधित कर रहे हैं, तो बस for each alignment in alignments कहें। अगर इससे कोई त्रुटि आती है क्योंकि संरेखण बदल रहा है, तो एक अपरिवर्तनीय प्रति बनाएं: for each alignment in list(alignments)

  • यदि आवश्यक नहीं है तो विशेष मामलों से बचें। निर्माण में आपके पास एक विशेष मामले की शुरुआत में एक परीक्षण सही है। लेकिन क्या वास्तव में इसकी आवश्यकता थी? क्या आपको इसके बिना एक ही परिणाम मिल जाएगा? अक्सर, जब आप कोड लिखते हैं, तो यह पता चला है कि आपको विशेष मामलों की आवश्यकता नहीं है। मेरे कोड में मुझे यह जांचने की आवश्यकता नहीं है कि कोई या कोई "लिंक" है या नहीं, क्योंकि यह उन सभी मामलों में ठीक काम करता है। यह आपको बग के बारे में चिंता करने और कम मौका देने के लिए कम कोड और कम चीजें देता है।

इन सब बातों की तुलना में अधिक है - आप obsessively साफ और व्यवस्थित रहना होगा। आपके पास बहुत सारे विचार हैं, लेकिन एक आधा आज़माएं, फिर दूसरे पर जाएं, उन्हें लिखें और उनके माध्यम से एक-एक करके काम करें। अन्यथा आप एक गड़बड़ी और कोड के साथ समाप्त होता है जिसे आप समझ में नहीं आता है। पहली बार में ऐसा लगता है कि आप समय बर्बाद कर रहे महसूस करता है, और आप जानना चाहते है कि एक परिणाम के रूप में आप तेजी से है क्योंकि आप कम समय उलझन में खर्च होते जा रहे हैं शुरू ...


जनरेटर

पर [मैं संशोधित कोड newline को अलग करने के लिए थोड़ा और print को कुछ स्थानों में जोड़ने के लिए थोड़ा सा कोड।]

पहले, क्या आपने कोड चलाया था? क्या यह आपकी तरह की चीज कर रहा है? क्या आप देख सकते हैं कि यह आपके साथ पहले से कैसे जुड़ता है? मेरा expand आपके buildAlignment (मुझे उम्मीद है) के समान है।

यदि आप इसे (नवीनतम संस्करण) चलाने आप देखेंगे:

: python2.7 recurse.py 
next 
expand 
next 
expand 
next 
expand 
next 
expand 
next 
expand 
next 
expand 
next 
expand 
next 
next 
... 

जो क्या हो रहा है के बारे में एक सुराग दे सकता है। "चाल" उपज कथन है - पायथन कंपाइलर देखता है और, सामान्य कार्य करने के बजाय, जनरेटर बनाता है।

जनरेटर एक बहुत ही अजीब चीज है। यह मूल रूप से आपका कार्य है (इस मामले में, expand), "बंडल अप" ताकि यह चरणों में चलाया जा सके। चल रहा है next() द्वारा किया जाता है और प्रत्येक बार yield तक पहुंचने पर फ़ंक्शन फिर से बंद हो जाता है।

तो trampoline इस अजीब बंडल को पारित किया गया है। और यह next() पर कॉल करता है। वह "जादू" फ़ंक्शन है जो फ़ंक्शन फ़ंक्शन शुरू करता है। इसलिए जब next को फ़ंक्शन शुरू होता है, तब तक यह yield तक पहुंच जाता है, जहां यह नया बंडल देता है। trampoline() आदेश तो पुराने बंडल बचत होती है और नया एक पर कार्य करना प्रारंभ, उस पर next() बुला, यह शुरू करने ... आदि आदि

जब एक जनरेटर "बातें खत्म हो जाता है ऐसा करने के लिए" यह StopIteration को जन्म देती है। इसलिए जब हम उस बिंदु तक पहुंच जाते हैं जहां अभिव्यक्ति और नहीं बढ़ सकती है तो हम trampoline() में अपवाद देखते हैं। उस समय हम अंतिम "पुराने" बंडल (हमारे stack में संग्रहीत) पर वापस आते हैं और उस पर next() पर कॉल करते हैं। यह बंडल पुन: प्रारंभ करता है, जहां से यह था (बस yield के बाद) और जारी रहता है, शायद while में एक और लूप कर रहा है, जब तक कि यह yield फिर से चलाता है (या बाहर चलाता है और StopIteration बढ़ाता है)।

अंत में, कोड ऐसा ही होता है जैसे yield वहां नहीं था! केवल अंतर यह है कि हम इन बंडलों को बनाते रहते हैं, और उन्हें वापस करते हैं। जो व्यर्थ लगता है। सिवाय इसके कि हम अब ढेर का उपयोग नहीं कर रहे हैं! क्योंकि बंडल लौटाया गया है "ढेर" का उपयोग नहीं किया जा रहा है! यही कारण है कि हमें अपने ढेर को प्रबंधित करने की आवश्यकता है (सूची stack) - अन्यथा यह जानने का कोई तरीका नहीं है कि पिछली कॉल क्या थी।

ठीक है, ठीक है, मुझे उम्मीद है कि आप इसे बिल्कुल समझने की उम्मीद नहीं करेंगे। हाँ, यह पागल है।अब आपको "पायथन जनरेटर" के लिए जाने और Google की आवश्यकता है। और इसका परीक्षण करने के लिए अपने स्वयं के कुछ कोड लिखें। लेकिन उम्मीद है कि यह रास्ता बताता है।


ओह, मैं भी कल रात सोच रहा था। और मुझे संदेह है कि यदि आप ढेर को थका रहे थे तो वास्तव में यह था क्योंकि आपके पास लूप हैं, न कि चेन इतने लंबे हैं। क्या आपने लूप माना है? ए-> बी, बी-> सी, सी-> ए, ....

+0

आपकी प्रतिक्रिया के लिए धन्यवाद। मैं डिफॉल्टडिक्ट से परिचित नहीं हूं, लेकिन जो मैंने अभी प्रलेखन में पढ़ा है, उसके आधार पर, वे सभी एंडिड्स को रखने के लिए एकदम सही टूल की तरह लगते हैं। क्या आप विस्तार कार्य को समझा सकते हैं, मुझे समझ में समस्या हो रही है कि रेखा क्या दर्शाती है? साथ ही, ऐसा प्रतीत होता है कि आप अभी भी इस कोड में रिकर्सन का उपयोग कर रहे हैं, इसे अभी जनरेटर में ले जाया गया है। किसी भी तरह से यह एक अच्छी शुरुआत हो सकती है - धन्यवाद। – Brian

+0

क्या आप इस कोड को व्यवस्थित और व्यवस्थित करने के तरीके के बारे में प्रतिक्रिया प्रदान करने के इच्छुक होंगे? मैं एक आत्म-सिखाया प्रोग्रामर हूं और मैं मानता हूं कि कभी-कभी मैं कोड को व्यवस्थित करने और सभी कोडों को सर्वोत्तम प्रथाओं को समझने के तरीके के साथ संघर्ष करता हूं। धन्यवाद। – Brian

+0

मैं उत्तर में और जोड़ दूंगा क्योंकि यहां उपलब्ध स्थान छोटा है। मैं नाश्ते करने जा रहा हूं, इसलिए मुझे थोड़ी देर दें ... –

संबंधित मुद्दे

 संबंधित मुद्दे