ऐसे प्रश्न का उत्तर देने की कोशिश करते समय आपको वास्तव में एक समाधान के रूप में प्रस्तावित कोड की सीमाएं देने की आवश्यकता होती है। यदि यह केवल प्रदर्शन के बारे में होता है तो मुझे बहुत ज्यादा बुरा नहीं लगेगा, लेकिन समाधान के रूप में प्रस्तावित अधिकांश कोड (स्वीकृत उत्तर सहित) किसी भी सूची को समतल करने में विफल होते हैं जिनकी गहराई 1000 से अधिक है।
जब मैं अधिकांश कोड कहता हूं मतलब है कि सभी कोड जो किसी भी प्रकार के पुनरावर्तन का उपयोग करते हैं (या मानक लाइब्रेरी फ़ंक्शन कहते हैं जो पुनरावर्ती है)। ये सभी कोड विफल हो जाते हैं क्योंकि किए गए प्रत्येक पुनरावर्ती कॉल के लिए, (कॉल) स्टैक एक इकाई से बढ़ता है, और (डिफ़ॉल्ट) अजगर कॉल स्टैक का आकार 1000 है।
यदि आप कॉल स्टैक से बहुत परिचित नहीं हैं, तो शायद निम्नलिखित मदद करेगा (अन्यथा आप केवल कार्यान्वयन पर स्क्रॉल कर सकते हैं )।
कॉल स्टैक आकार और पुनरावर्ती प्रोग्रामिंग (कालकोठरी सादृश्य)
खजाना ढूंढना और बाहर निकलना
कल्पना कीजिए कि आप एक विशाल कालकोठरी में गिने हुए कमरों के साथ एक खजाने की तलाश कर रहे हैं। आपको जगह का पता नहीं है लेकिन आपको कुछ संकेत हैं कि खजाने को कैसे खोजना है। प्रत्येक संकेत एक पहेली है (कठिनाई भिन्न होती है, लेकिन आप अनुमान नहीं लगा सकते हैं कि वे कितने कठिन होंगे)। आप समय बचाने के लिए एक रणनीति के बारे में थोड़ा सोचने का फैसला करते हैं, आप दो अवलोकन करते हैं:
- खजाना ढूंढना कठिन (लंबा) है क्योंकि आपको वहां पहुंचने के लिए (संभावित रूप से कठिन) पहेलियों को हल करना होगा।
- एक बार खजाना मिल जाने के बाद, प्रवेश द्वार पर वापस जाना आसान हो सकता है, आपको बस उसी दिशा का दूसरी दिशा में उपयोग करना होगा (हालाँकि यह आपके पथ को याद करने के लिए थोड़ी मेमोरी की आवश्यकता है)।
कालकोठरी में प्रवेश करते समय, आप यहां एक छोटी नोटबुक पर ध्यान देते हैं। आप एक पहेली को हल करने के बाद बाहर निकलने वाले हर कमरे को लिखने के लिए इसका उपयोग करने का निर्णय लेते हैं (जब एक नए कमरे में प्रवेश करते हैं), इस तरह से आप प्रवेश द्वार पर वापस आ पाएंगे। यह एक प्रतिभाशाली विचार है, आप एक प्रतिशत भी खर्च नहीं करेंगे अपनी रणनीति को लागू करने में ।
आप काल कोठरी में प्रवेश करते हैं, पहले 1001 पहेलियों को बड़ी सफलता के साथ हल करते हैं, लेकिन यहां कुछ ऐसा आता है जिसकी आपने योजना नहीं बनाई थी, आपके पास उधार ली गई नोटबुक में कोई जगह नहीं बची है। आप छोड़ने का फैसला करते हैं अपनी खोज करते हैं क्योंकि आप कालकोठरी के अंदर हमेशा के लिए खो जाने की तुलना में खजाना नहीं पसंद करते हैं (जो वास्तव में स्मार्ट दिखता है)।
एक पुनरावर्ती कार्यक्रम को निष्पादित करना
मूल रूप से, यह खजाने को खोजने के समान सटीक बात है। कालकोठरी कंप्यूटर की मेमोरी है , अब आपका लक्ष्य एक खजाना ढूंढना नहीं है, बल्कि किसी फ़ंक्शन ( किसी दिए गए x के लिए f (x) का पता लगाना ) की गणना करना है । संकेत बस उप-रूटीन हैं जो आपको एफ (एक्स) को हल करने में मदद करेंगे । आपकी रणनीति कॉल स्टैक रणनीति के समान है, नोटबुक स्टैक है, कमरे फ़ंक्शंस पते हैं:
x = ["over here", "am", "I"]
y = sorted(x) # You're about to enter a room named `sorted`, note down the current room address here so you can return back: 0x4004f4 (that room address looks weird)
# Seems like you went back from your quest using the return address 0x4004f4
# Let's see what you've collected
print(' '.join(y))
कालकोठरी में आपके सामने जो समस्या है वही यहाँ होगी, कॉल स्टैक का परिमित आकार (यहाँ 1000) है और इसलिए, यदि आप बिना वापस लौटते हुए बहुत सारे कार्य करते हैं तो आप कॉल स्टैक को भर देंगे और एक त्रुटि होगी जो देखो जैसे "प्रिय साहसी, मुझे बहुत खेद है, लेकिन आपकी नोटबुक भरी हुई है" : जो कि एक बार - बार-बार कॉल करता है - आप गणना खत्म होने तक और खजाने के खत्म होने तक प्रवेश करेंगे । जब तक आप उस स्थान पर वापस नहीं जाते हैं जहां आपने पहली जगह पर बुलाया था। कॉल स्टैक को कभी भी किसी भी चीज से मुक्त नहीं किया जाएगा, जहां अंत तक यह एक के बाद एक सभी रिटर्न एड्रेस से मुक्त हो जाएगा।RecursionError: maximum recursion depth exceeded
। ध्यान दें कि आपको कॉल स्टैक को भरने के लिए पुनरावृत्ति की आवश्यकता नहीं है, लेकिन यह बहुत ही संभावना नहीं है कि एक गैर-पुनरावर्ती कार्यक्रम 1000 कार्यों को कभी भी वापस न करे। यह समझना भी महत्वपूर्ण है कि एक बार जब आप किसी फ़ंक्शन से लौटते हैं, तो कॉल स्टैक उपयोग किए गए पते से मुक्त हो जाता है (इसलिए नाम "स्टैक", रिटर्न पता किसी फ़ंक्शन में प्रवेश करने से पहले धकेल दिया जाता है और वापस लौटते समय बाहर निकाला जाता है)। एक साधारण पुनरावृत्ति के विशेष मामले में (एक फ़ंक्शनf
f
f
f
इस मुद्दे से कैसे बचें?
यह वास्तव में बहुत आसान है: "यदि आप नहीं जानते कि यह कितना गहरा जा सकता है" पुनरावृत्ति का उपयोग न करें। यह हमेशा सही नहीं होता है क्योंकि कुछ मामलों में टेल कॉल रिक्रिएशन को ऑप्टिमाइज़ (TCO) किया जा सकता है । लेकिन अजगर में, यह मामला नहीं है, और यहां तक कि "अच्छी तरह से लिखा गया" पुनरावर्ती फ़ंक्शन स्टैक उपयोग का अनुकूलन नहीं करेगा । इस सवाल के बारे में गुइडो से एक दिलचस्प पोस्ट है: टेल रिकर्सन एलिमिनेशन ।
एक तकनीक है जिसे आप किसी भी पुनरावर्ती कार्य को पुनरावृत्त बनाने के लिए उपयोग कर सकते हैं, इस तकनीक को हम अपनी खुद की नोटबुक ला सकते हैं । उदाहरण के लिए, हमारे विशेष मामले में हम बस एक सूची खोज रहे हैं, एक कमरे में प्रवेश करना एक सबलिस्ट में प्रवेश करने के बराबर है, सवाल यह है कि आपको खुद से पूछना चाहिए कि मैं एक सूची से इसकी मूल सूची में वापस कैसे आ सकता हूं? जवाब यह जटिल नहीं है, जब तक stack
खाली न हो, तब तक निम्नलिखित दोहराएं :
- वर्तमान सूची धक्का
address
और index
एक में stack
जब एक नया sublist में प्रवेश (नोट एक सूची पता + सूचकांक भी एक पता है कि, इसलिए हम सिर्फ कॉल स्टैक द्वारा इस्तेमाल किया ठीक उसी तकनीक का उपयोग करें);
- जब भी कोई वस्तु मिलती है,
yield
वह (या उन्हें सूची में जोड़ें);
- एक सूची पूरी तरह से खोजे जाने के बाद,
stack
वापसी address
(और index
) का उपयोग करके मूल सूची में वापस जाएं ।
यह भी ध्यान दें कि यह एक पेड़ में डीएफएस के बराबर है जहां कुछ नोड उप-कलाकार हैं A = [1, 2]
और कुछ सरल आइटम हैं: 0, 1, 2, 3, 4
(के लिए L = [0, [1,2], 3, 4]
)। पेड़ इस तरह दिखता है:
L
|
-------------------
| | | |
0 --A-- 3 4
| |
1 2
डीएफएस ट्रैवर्सल प्री-ऑर्डर है: एल, 0, ए, 1, 2, 3, 4. याद रखें, एक क्रमिक डीएफएस को लागू करने के लिए आपको एक स्टैक की भी "आवश्यकता" है। निम्न राज्यों ( stack
और flat_list
) के लिए होने वाले परिणाम से पहले मैंने जो कार्यान्वयन प्रस्तावित किया था :
init.: stack=[(L, 0)]
**0**: stack=[(L, 0)], flat_list=[0]
**A**: stack=[(L, 1), (A, 0)], flat_list=[0]
**1**: stack=[(L, 1), (A, 0)], flat_list=[0, 1]
**2**: stack=[(L, 1), (A, 1)], flat_list=[0, 1, 2]
**3**: stack=[(L, 2)], flat_list=[0, 1, 2, 3]
**3**: stack=[(L, 3)], flat_list=[0, 1, 2, 3, 4]
return: stack=[], flat_list=[0, 1, 2, 3, 4]
इस उदाहरण में, स्टैक अधिकतम आकार 2 है, क्योंकि इनपुट सूची (और इसलिए पेड़) की गहराई 2 है।
कार्यान्वयन
कार्यान्वयन के लिए, अजगर में आप सरल सूचियों के बजाय पुनरावृत्तियों का उपयोग करके थोड़ा सा सरल कर सकते हैं। (उप) पुनरावृत्तियों का संदर्भ उप -सूची रिटर्न पते (सूची पता और सूचकांक दोनों के होने के बजाय ) को संग्रहीत करने के लिए उपयोग किया जाएगा । यह एक बड़ा अंतर नहीं है लेकिन मुझे लगता है कि यह अधिक पठनीय है (और थोड़ा तेज़ भी):
def flatten(iterable):
return list(items_from(iterable))
def items_from(iterable):
cursor_stack = [iter(iterable)]
while cursor_stack:
sub_iterable = cursor_stack[-1]
try:
item = next(sub_iterable)
except StopIteration: # post-order
cursor_stack.pop()
continue
if is_list_like(item): # pre-order
cursor_stack.append(iter(item))
elif item is not None:
yield item # in-order
def is_list_like(item):
return isinstance(item, list)
इसके अलावा, ध्यान दें कि is_list_like
मेरे पास है isinstance(item, list)
, जिसे अधिक इनपुट प्रकारों को संभालने के लिए बदला जा सकता है, यहां मैं सिर्फ सबसे सरल संस्करण चाहता था जहां (iterable) सिर्फ एक सूची है। लेकिन आप यह भी कर सकते हैं:
def is_list_like(item):
try:
iter(item)
return not isinstance(item, str) # strings are not lists (hmm...)
except TypeError:
return False
यह स्ट्रिंग्स को "सरल आइटम" मानता है और इसलिए flatten_iter([["test", "a"], "b])
वापस आ जाएगा ["test", "a", "b"]
और नहीं ["t", "e", "s", "t", "a", "b"]
। उस मामले में, iter(item)
प्रत्येक आइटम पर दो बार बुलाया जाता है, आइए हम इसे क्लीनर बनाने के लिए पाठक के लिए एक अभ्यास का नाटक करते हैं।
परीक्षण और अन्य कार्यान्वयन पर टिप्पणी
अंत में, याद रखें कि आप एक असीम रूप से नेस्टेड सूची L
का उपयोग नहीं कर सकते print(L)
क्योंकि आंतरिक रूप से यह __repr__
( RecursionError: maximum recursion depth exceeded while getting the repr of an object
) ( ) के लिए पुनरावर्ती कॉल का उपयोग करेगा । उसी कारण से, flatten
शामिल करने के समाधान str
एक ही त्रुटि संदेश के साथ विफल हो जाएंगे।
यदि आपको अपने समाधान का परीक्षण करने की आवश्यकता है, तो आप एक साधारण नेस्टेड सूची बनाने के लिए इस फ़ंक्शन का उपयोग कर सकते हैं:
def build_deep_list(depth):
"""Returns a list of the form $l_{depth} = [depth-1, l_{depth-1}]$
with $depth > 1$ and $l_0 = [0]$.
"""
sub_list = [0]
for d in range(1, depth):
sub_list = [d, sub_list]
return sub_list
कौन देता है: build_deep_list(5)
>>> [4, [3, [2, [1, [0]]]]]
।