एक स्ट्रिंग की तर्ज पर Iterate करें


119

मेरे पास एक बहु-पंक्ति स्ट्रिंग इस तरह परिभाषित है:

foo = """
this is 
a multi-line string.
"""

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

lineiterator = iter(foo.splitlines())

क्या ऐसा करने का अधिक प्रत्यक्ष तरीका है? इस परिदृश्य में स्ट्रिंग को बंटवारे के लिए एक बार पीछे करना पड़ता है, और फिर पार्सर द्वारा फिर से। यह मेरे परीक्षण के मामले में कोई फर्क नहीं पड़ता है, क्योंकि स्ट्रिंग बहुत कम है, मैं सिर्फ जिज्ञासा से बाहर पूछ रहा हूं। इस तरह के सामान के लिए पाइथन के पास इतने उपयोगी और कुशल बिल्ट-इन हैं, लेकिन मुझे ऐसा कुछ भी नहीं मिला जो इस जरूरत के अनुरूप हो।


12
आप जानते हैं कि आप foo.splitlines()सही पर पुनरावृति कर सकते हैं ?
साइलेंटगॉस्ट

"फिर से पार्सर" से आपका क्या मतलब है?
डेनबेन

4
@SilentGhost: मुझे लगता है कि बिंदु दो बार स्ट्रिंग को पुनरावृत्त नहीं करना है। एक बार जब यह splitlines()विधि द्वारा पुनरावृत्त किया जाता है और दूसरी बार इस विधि के परिणाम से अधिक पुनरावृत्त होता है।
फेलिक्स क्लिंग

2
क्या कोई विशेष कारण है कि स्प्लिटलाइन () डिफ़ॉल्ट रूप से पुनरावृत्ति नहीं करता है? मुझे लगा कि इसका चलन आम तौर पर पुनरावृत्तियों के लिए था। या यह केवल हुकुम () जैसे विशिष्ट कार्यों के लिए सही है?
सेर्नो

जवाबों:


144

यहां तीन संभावनाएं हैं:

foo = """
this is 
a multi-line string.
"""

def f1(foo=foo): return iter(foo.splitlines())

def f2(foo=foo):
    retval = ''
    for char in foo:
        retval += char if not char == '\n' else ''
        if char == '\n':
            yield retval
            retval = ''
    if retval:
        yield retval

def f3(foo=foo):
    prevnl = -1
    while True:
      nextnl = foo.find('\n', prevnl + 1)
      if nextnl < 0: break
      yield foo[prevnl + 1:nextnl]
      prevnl = nextnl

if __name__ == '__main__':
  for f in f1, f2, f3:
    print list(f())

इसे चलाना मुख्य स्क्रिप्ट के रूप में पुष्टि करता है कि तीन कार्य बराबर हैं। साथ timeit(और एक * 100के लिए fooऔर अधिक सटीक माप के लिए पर्याप्त तार पाने के लिए):

$ python -mtimeit -s'import asp' 'list(asp.f3())'
1000 loops, best of 3: 370 usec per loop
$ python -mtimeit -s'import asp' 'list(asp.f2())'
1000 loops, best of 3: 1.36 msec per loop
$ python -mtimeit -s'import asp' 'list(asp.f1())'
10000 loops, best of 3: 61.5 usec per loop

ध्यान दें कि हमें list()यह सुनिश्चित करने के लिए कॉल की आवश्यकता है कि ट्रैवर्स को ट्रैवर्स किया गया है, न कि केवल निर्मित।

IOW, भोली कार्यान्वयन बहुत तेज़ है यह मज़ेदार भी नहीं है: findकॉल के साथ मेरे प्रयास की तुलना में 6 गुना तेज है , जो बदले में निचले स्तर के दृष्टिकोण से 4 गुना तेज है।

बनाए रखने के लिए सबक: माप हमेशा एक अच्छी चीज है (लेकिन सटीक होना चाहिए); स्ट्रिंग तरीकों पसंद splitlinesबहुत तेजी से तरीकों से लागू कर रहे हैं; बहुत कम स्तर पर प्रोग्रामिंग द्वारा तार एक साथ रखना (लूप ऑफ द लूप्स)+= बहुत छोटे टुकड़ों के ) काफी धीमा हो सकता है।

संपादित करें : @ याकूब का प्रस्ताव, दूसरों के रूप में एक ही परिणाम देने के लिए थोड़ा संशोधित (रेखा पर रिक्त स्थान रखा जाता है), यानी:

from cStringIO import StringIO

def f4(foo=foo):
    stri = StringIO(foo)
    while True:
        nl = stri.readline()
        if nl != '':
            yield nl.strip('\n')
        else:
            raise StopIteration

मापने देता है:

$ python -mtimeit -s'import asp' 'list(asp.f4())'
1000 loops, best of 3: 406 usec per loop

.findआधारित दृष्टिकोण के रूप में बहुत अच्छा नहीं है - फिर भी, ध्यान में रखने योग्य है क्योंकि यह छोटे ऑफ-बाय-वन बग (किसी भी लूप जहां आपको +1 और -1 की घटनाएँ दिखाई देती हैं, जैसे कि मेरे f3ऊपर, कम हो सकता है) एक-एक संदेह को ट्रिगर करें - और इसलिए कई लूप्स होने चाहिए जिनमें इस तरह की ट्वीक्स की कमी हो और उन्हें होना चाहिए - हालांकि मेरा मानना ​​है कि मेरा कोड भी सही है क्योंकि मैं अन्य कार्यों के साथ इसके आउटपुट की जांच करने में सक्षम था)।

लेकिन विभाजित-आधारित दृष्टिकोण अभी भी नियम है।

एक तरफ: संभवतः के लिए बेहतर शैली f4होगी:

from cStringIO import StringIO

def f4(foo=foo):
    stri = StringIO(foo)
    while True:
        nl = stri.readline()
        if nl == '': break
        yield nl.strip('\n')

कम से कम, यह थोड़ा कम क्रिया है। अनुगामी स्ट्रिपिंग की आवश्यकता \nदुर्भाग्य से और whileपाश के तेजी से प्रतिस्थापन को रोकती है return iter(stri)(जिस iterभाग पायथन के आधुनिक संस्करणों में बेमानी है, मेरा मानना ​​है कि 2.3 या 2.4 के बाद से, लेकिन यह भी सहज है)। शायद कोशिश करने लायक भी:

    return itertools.imap(lambda s: s.strip('\n'), stri)

या इसके रूपांतर - लेकिन मैं यहाँ रोक रहा हूँ क्योंकि यह एक बहुत ही सैद्धांतिक अभ्यास है जो stripसबसे सरल और सबसे तेज़ आधारित है।


इसके अलावा, (line[:-1] for line in cStringIO.StringIO(foo))बहुत तेज है; भोली कार्यान्वयन के रूप में लगभग उतना ही तेज़, लेकिन काफी नहीं।
मैट एंडरसन

इस बेहतरीन जवाब के लिए धन्यवाद। मुझे लगता है कि यहां मुख्य सबक है (जैसा कि मैं अजगर के लिए नया हूं) timeitएक आदत का उपयोग करना है।
ब्योर्न पोलेक्स

@स्पेस, हां, समय अच्छा है, किसी भी समय आप प्रदर्शन के बारे में परवाह करते हैं (ध्यान से इसका उपयोग करना सुनिश्चित करें, जैसे कि इस मामले में मेरे नोट listको वास्तव में सभी प्रासंगिक भागों को कॉल करने की आवश्यकता के बारे में देखें ! -)।
एलेक्स मार्टेली

6
मेमोरी खपत के बारे में क्या? split()स्पष्ट रूप से प्रदर्शन के लिए स्मृति ट्रेडों, सूची की संरचनाओं के अलावा सभी वर्गों की एक प्रति पकड़े।
ivan_pozdeev

3
मैं पहली बार आपकी टिप्पणी से वास्तव में भ्रमित था क्योंकि आपने उनके कार्यान्वयन और क्रमांकन के विपरीत क्रम में समय के परिणामों को सूचीबद्ध किया था। = P
jamesdlin

53

मुझे यकीन नहीं है कि आप "तो फिर से पार्सर" से क्या मतलब है। बंटवारे के बाद किया गया है, वहाँ स्ट्रिंग का कोई और अधिक नहीं है , केवल विभाजित स्ट्रिंग्स की सूची का एक ट्रावेल है । यह संभवतः इसे पूरा करने का सबसे तेज़ तरीका होगा, जब तक कि आपके स्ट्रिंग का आकार बिल्कुल विशाल नहीं है। तथ्य यह है कि अजगर अपरिवर्तनीय तारों का उपयोग करता है इसका मतलब है कि आपको हमेशा एक नया स्ट्रिंग बनाना होगा , इसलिए इसे किसी भी बिंदु पर किया जाना चाहिए।

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

हालाँकि, यदि आपके पास पहले से ही मेमोरी में एक बहुत बड़ा स्ट्रिंग है, तो एक दृष्टिकोण स्ट्रींगियो का उपयोग करना होगा, जो एक स्ट्रिंग की तरह एक फाइल-इंटरफ़ेस को प्रस्तुत करता है, जिसमें लाइन द्वारा पुनरावृत्ति की अनुमति देना (आंतरिक रूप से अगली नई लाइन को खोजने के लिए .find का उपयोग करना) शामिल है। फिर आप प्राप्त करें:

import StringIO
s = StringIO.StringIO(myString)
for line in s:
    do_something_with(line)

5
नोट: अजगर 3 के लिए आपको इसके लिए ioपैकेज का उपयोग करना होगा, जैसे कि io.StringIOइसके बजाय का उपयोग करें StringIO.StringIODocs.python.org/3/library/io.html
Attila123

उपयोग करना StringIOभी उच्च प्रदर्शन सार्वभौमिक न्यूलाइन हैंडलिंग प्राप्त करने का एक अच्छा तरीका है।
मार्टिन

3

यदि मैं Modules/cStringIO.cसही ढंग से पढ़ता हूं , तो यह काफी कुशल होना चाहिए (हालांकि कुछ हद तक क्रिया):

from cStringIO import StringIO

def iterbuf(buf):
    stri = StringIO(buf)
    while True:
        nl = stri.readline()
        if nl != '':
            yield nl.strip()
        else:
            raise StopIteration

3

रेगेक्स-आधारित खोज जनरेटर के दृष्टिकोण से कभी-कभी तेज होता है:

RRR = re.compile(r'(.*)\n')
def f4(arg):
    return (i.group(1) for i in RRR.finditer(arg))

2
यह प्रश्न एक विशिष्ट परिदृश्य के बारे में है, इसलिए यह एक साधारण बेंचमार्क दिखाने में मदद करेगा, जैसे कि शीर्ष स्कोरिंग उत्तर ने किया है।
ब्योर्न पोलेक्स

1

मुझे लगता है कि आप अपना रोल कर सकते हैं:

def parse(string):
    retval = ''
    for char in string:
        retval += char if not char == '\n' else ''
        if char == '\n':
            yield retval
            retval = ''
    if retval:
        yield retval

मुझे यकीन नहीं है कि यह कार्यान्वयन कितना कुशल है, लेकिन यह केवल एक बार आपके स्ट्रिंग पर पुनरावृति करेगा।

मम्म, जनरेटर।

संपादित करें:

बेशक आप यह भी जोड़ना चाहते हैं कि आप किस प्रकार की पार्सिंग क्रियाएँ करना चाहते हैं, लेकिन यह बहुत सरल है।


लंबी लाइनों के लिए सुंदर अक्षम ( +=भाग में सबसे खराब O(N squared)प्रदर्शन होता है, हालांकि कई कार्यान्वयन चालें कम करने की कोशिश करती हैं कि जब संभव हो)।
एलेक्स मार्टेली

हाँ - मैं अभी उस बारे में जान रहा हूँ। क्या यह तेजी से वर्णों की सूची में शामिल होगा और फिर '' .join (चर) उन्हें? या यह कि एक प्रयोग मुझे स्वयं करना चाहिए? ;)
वेन वर्नर

कृपया अपने आप को मापें, यह शिक्षाप्रद है - और ओपी के उदाहरण में दोनों छोटी लाइनों की कोशिश करना सुनिश्चित करें, और लंबे लोगों को! -)
एलेक्स मार्टेली

शॉर्ट स्ट्रिंग्स के लिए (<~ 40 chars) + = वास्तव में तेज है, लेकिन सबसे जल्दी मामले को हिट करता है। लंबे समय तक तार के लिए, .joinविधि वास्तव में ओ (एन) जटिलता की तरह दिखती है। चूंकि मुझे एसओ पर की गई विशेष तुलना अभी तक नहीं मिली, इसलिए मैंने एक प्रश्न शुरू किया stackoverflow.com/questions/3055477/… (जो आश्चर्यजनक रूप से मेरे खुद के मुकाबले अधिक जवाब मिला!)
वेन वर्नर

0

आप "एक फ़ाइल" पर पुनरावृति कर सकते हैं, जो अनुगामी न्यूलाइन वर्ण सहित लाइनें पैदा करता है। एक स्ट्रिंग से "आभासी फ़ाइल" बनाने के लिए, आप इसका उपयोग कर सकते हैं StringIO:

import io  # for Py2.7 that would be import cStringIO as io

for line in io.StringIO(foo):
    print(repr(line))
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.