दूसरा पायथन जेनरेटर छोटा होने के साथ: कैसे चुपचाप खाये जाने वाले तत्व को पुनः प्राप्त करना


50

मैं (संभावित) अलग लंबाई के 2 जनरेटर के साथ पार्स करना चाहता हूं zip:

for el1, el2 in zip(gen1, gen2):
    print(el1, el2)

हालांकि, अगर gen2कम तत्व हैं, तो एक अतिरिक्त तत्व gen1"भस्म" है।

उदाहरण के लिए,

def my_gen(n:int):
    for i in range(n):
        yield i

gen1 = my_gen(10)
gen2 = my_gen(8)

list(zip(gen1, gen2))  # Last tuple is (7, 7)
print(next(gen1))  # printed value is "9" => 8 is missing

gen1 = my_gen(8)
gen2 = my_gen(10)

list(zip(gen1, gen2))  # Last tuple is (7, 7)
print(next(gen2))  # printed value is "8" => OK

जाहिर है, एक मूल्य गायब है ( 8मेरे पिछले उदाहरण में) क्योंकि gen1पढ़ा जाता है (इस प्रकार मूल्य पैदा करता है 8) इससे पहले कि यह पता चलता gen2है कि इसमें और अधिक तत्व नहीं हैं। लेकिन यह मान ब्रह्मांड में गायब हो जाता है। जब gen2"लंबा" होता है, तो ऐसी कोई "समस्या" नहीं होती है।

प्रश्न : क्या इस अनुपलब्ध मूल्य को पुनः प्राप्त करने का एक तरीका है (अर्थात 8मेरे पिछले उदाहरण में)? ... आदर्श रूप से तर्कों की एक चर संख्या के साथ (जैसे zipकरता है)।

नोट : मैंने वर्तमान में उपयोग करके एक और तरीके से लागू किया है, itertools.zip_longestलेकिन मुझे वास्तव में आश्चर्य है कि इस लापता मूल्य का उपयोग zipया समकक्ष कैसे प्राप्त किया जाए ।

नोट 2 : मैंने इस REPL में विभिन्न कार्यान्वयन के कुछ परीक्षण बनाए हैं, यदि आप एक नया कार्यान्वयन प्रस्तुत करना चाहते हैं और प्रयास करना चाहते हैं :) https://repl.it/@jfthuong/MadPhysicistChester


19
डॉक्स ध्यान दें कि "ज़िप () का उपयोग केवल असमान लंबाई इनपुट के साथ किया जाना चाहिए जब आप अनुगामी के बारे में परवाह नहीं करते हैं, लंबे iterables से बेजोड़ मान। यदि वे मान महत्वपूर्ण हैं, तो इसके बजाय itertools.zip_longest () का उपयोग करें।"
16

2
@ Ch3steR। लेकिन सवाल का "क्यों" से कोई लेना-देना नहीं है। यह सचमुच पढ़ता है "क्या इस लापता मूल्य को पुनः प्राप्त करने का एक तरीका है ...?" ऐसा लगता है कि सभी जवाब लेकिन मेरा आसानी से उस हिस्से को पढ़ना भूल गए।
पागल भौतिकवादी

@MadPhysicist वास्तव में अजीब है। मैंने उस पहलू पर स्पष्ट होने के लिए प्रश्न को फिर से परिभाषित किया।
जीन-फ्रेंकोइस टी।

1
मूल समस्या यह है कि किसी जनरेटर में पीछे धकेलने या धक्का देने का कोई तरीका नहीं है। तो एक बार zip()से पढ़ा 8है gen1, यह चला गया है।
बमर

1
@ बरम जरूर, हम सब उस पर सहमत थे। सवाल यह था कि इसे कहीं और कैसे स्टोर किया जाए ताकि इसका इस्तेमाल किया जा सके।
जीन-फ्रेंकोइस टी।

जवाबों:


28

एक तरीका यह होगा कि आप एक ऐसे जेनरेटर को लागू करें जिससे आपको अंतिम मूल्य मिल सके:

class cache_last(collections.abc.Iterator):
    """
    Wraps an iterable in an iterator that can retrieve the last value.

    .. attribute:: obj

       A reference to the wrapped iterable. Provided for convenience
       of one-line initializations.
    """
    def __init__(self, iterable):
        self.obj = iterable
        self._iter = iter(iterable)
        self._sentinel = object()

    @property
    def last(self):
        """
        The last object yielded by the wrapped iterator.

        Uninitialized iterators raise a `ValueError`. Exhausted
        iterators raise a `StopIteration`.
        """
        if self.exhausted:
            raise StopIteration
        return self._last

    @property
    def exhausted(self):
        """
        `True` if there are no more elements in the iterator.
        Violates EAFP, but convenient way to check if `last` is valid.
        Raise a `ValueError` if the iterator is not yet started.
        """
        if not hasattr(self, '_last'):
            raise ValueError('Not started!')
        return self._last is self._sentinel

    def __next__(self):
        """
        Retrieve, record, and return the next value of the iteration.
        """
        try:
            self._last = next(self._iter)
        except StopIteration:
            self._last = self._sentinel
            raise
        # An alternative that has fewer lines of code, but checks
        # for the return value one extra time, and loses the underlying
        # StopIteration:
        #self._last = next(self._iter, self._sentinel)
        #if self._last is self._sentinel:
        #    raise StopIteration
        return self._last

    def __iter__(self):
        """
        This object is already an iterator.
        """
        return self

इसका उपयोग करने के लिए, इन इनपुट्स को इसमें शामिल करें zip:

gen1 = cache_last(range(10))
gen2 = iter(range(8))
list(zip(gen1, gen2))
print(gen1.last)
print(next(gen1)) 

gen2एक पुनरावृत्त के बजाय एक इटरेटर बनाना महत्वपूर्ण है , इसलिए आप जान सकते हैं कि कौन सा समाप्त हो गया था। अगरgen2 थकावट होती है, तो आपको जांच की आवश्यकता नहीं है gen1.last

एक और तरीका यह होगा कि अलग-अलग पुनरावृत्तियों के बजाय पुनरावृत्तियों के एक परिवर्तनशील अनुक्रम को स्वीकार करने के लिए जिप को ओवरराइड किया जाए। यह आपको एक जंजीर संस्करण के साथ पुनरावृत्तियों को बदलने की अनुमति देगा जिसमें आपका "संक्षिप्त" आइटम शामिल है:

def myzip(iterables):
    iterators = [iter(it) for it in iterables]
    while True:
        items = []
        for it in iterators:
            try:
                items.append(next(it))
            except StopIteration:
                for i, peeked in enumerate(items):
                    iterables[i] = itertools.chain([peeked], iterators[i])
                return
            else:
                yield tuple(items)

gens = [range(10), range(8)]
list(myzip(gens))
print(next(gens[0]))

यह दृष्टिकोण कई कारणों से समस्याग्रस्त है। न केवल यह मूल चलने योग्य खो देगा, लेकिन यह किसी भी उपयोगी गुण को खो देगा मूल वस्तु को किसी chainवस्तु के साथ प्रतिस्थापित करके हो सकता है ।


@MadPhysicist। अपने उत्तर से प्यार करें cache_last, और इस तथ्य के साथ कि यह nextव्यवहार में परिवर्तन नहीं करता है ... इतना बुरा कि यह सममित नहीं है (स्विचिंग gen1और gen2जिप में अलग-अलग परिणाम होंगे)। खीर
जीन-फ्रेंकोइस टी।

1
@ जीन फ्रेंकोइस। मैंने lastइसे समाप्त होने के बाद कॉल करने के लिए ठीक से प्रतिक्रिया करने के लिए पुनरावृत्ति अद्यतन किया है । यह पता लगाने में मदद करनी चाहिए कि आपको अंतिम मूल्य की आवश्यकता है या नहीं। इसके अलावा यह अधिक उत्पादन-वाई बनाता है।
मैड फिजिसिस्ट

@MadPhysicist मैं कोड और के उत्पादन में भाग गया print(gen1.last) print(next(gen1)) हैNone and 9
Ch3steR

कुछ विरोधाभास और सभी के साथ @MadPhysicist। अच्छा;) मैं बाद में जाँच करूँगा जब मेरे पास समय होगा। बिताए गए समय के लिए धन्यवाद
जीन-फ्रेंकोइस टी।

@ Ch3steR। पकड़ने के लिए धन्यवाद। मैं बहुत उत्साहित हो गया और वापसी विवरण को हटा दिया last
मैड फिजिसिस्ट

17

यह डॉक्सzip में दिए गए कार्यान्वयन के बराबर है

def zip(*iterables):
    # zip('ABCD', 'xy') --> Ax By
    sentinel = object()
    iterators = [iter(it) for it in iterables]
    while iterators:
        result = []
        for it in iterators:
            elem = next(it, sentinel)
            if elem is sentinel:
                return
            result.append(elem)
        yield tuple(result)

अपने 1 उदाहरण में gen1 = my_gen(10)और gen2 = my_gen(8)। दोनों जनरेटर 7 वीं पुनरावृत्ति तक भस्म हो जाते हैं। अब 8 वें पुनरावृत्ति gen1कॉल में elem = next(it, sentinel)जो 8 लौटता है, लेकिन जब gen2कॉल करता है तो elem = next(it, sentinel)यह वापस लौटता है sentinel(क्योंकि यह gen2समाप्त हो जाता है) औरif elem is sentinel संतुष्ट है और फ़ंक्शन रिटर्न और स्टॉप निष्पादित करता है। अब next(gen1)लौटता है ९।

अपने 2 उदाहरण में gen1 = gen(8)और gen2 = gen(10)। दोनों जनरेटर 7 वीं पुनरावृत्ति तक भस्म हो जाते हैं। अब 8 वें पुनरावृत्ति gen1कॉल में elem = next(it, sentinel)जो रिटर्न sentinel(क्योंकि इस बिंदु gen1पर समाप्त हो गया है) if elem is sentinelसंतुष्ट है और फ़ंक्शन रिटर्न को निष्पादित करता है और बंद हो जाता है। अब next(gen2)लौटता है 8।

मैड फिजिसिस्ट के जवाब से प्रेरित होकर , आप इसका Genमुकाबला करने के लिए इस आवरण का उपयोग कर सकते हैं :

संपादित करें : जीन-फ्रेंकोइस टी द्वारा इंगित मामलों को संभालने के लिए

एक बार जब यह इटैलर से हमेशा के लिए चला जाता है, तो इट्रेटर से एक मूल्य का उपभोग किया जाता है और पुनरावृत्तियों के लिए इसे वापस जोड़ने के लिए पुनरावृत्तियों के लिए कोई इन-प्लेस म्यूटिंग विधि नहीं होती है। चारों ओर एक काम अंतिम खपत मूल्य को संग्रहीत करना है।

class Gen:
    def __init__(self,iterable):
        self.d = iter(iterable)
        self.sentinal = object()
        self.prev = self.sentinal
    def __iter__(self):
        return self
    @property
    def last_val_consumed(self):
        if self.prev is None:
            raise StopIteration
        if self.prev == self.sentinal:
            raise ValueError('Nothing has been consumed')
        return self.prev
    def __next__(self):
        self.prev = next(self.d,None)
        if self.prev is None:
            raise StopIteration
        return self.prev

उदाहरण:

# When `gen1` is larger than `gen2`
gen1 = Gen(range(10))
gen2 = Gen(range(8))
list(zip(gen1,gen2))
# [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7)]
gen1.last_val_consumed
# 8 #as it was the last values consumed
next(gen1)
# 9
gen1.last_val_consumed
# 9

# 2. When `gen1` or `gen2` is empty
gen1 = Gen(range(0))
gen2 = Gen(range(5))
list(zip(gen1,gen2))
gen1.last_val_consumed
# StopIteration error is raised
gen2.last_val_consumed
# ValueError is raised saying `ValueError: Nothing has been consumed`

इस समस्या पर बिताए गए समय के लिए धन्यवाद @ Ch3steR। MadPhysicist समाधान के आपके संशोधन में कई सीमाएँ हैं: # 1। अगर gen1 = cache_last(range(0))और gen2 = cache_last(range(2))फिर करने के बाद list(zip(gen1, gen2), कॉल करने के next(gen2)लिए एक उठाना होगा AttributeError: 'cache_last' object has no attribute 'prev'। # 2। यदि gen1, gen2 से अधिक है, तो सभी तत्वों का उपभोग करने के बाद, next(gen2)इसके बजाय अंतिम मान लौटाते रहेंगे StopIteration। मैं MadPhysicist जवाब और जवाब को चिह्नित करूंगा। धन्यवाद!
जीन-फ्रेंकोइस टी।

@ जीन FrancoisT। हाँ मान गया। आपको जवाब के रूप में उसके जवाब को चिह्नित करना चाहिए। इसकी सीमाएँ हैं। मैं सभी मामलों का सामना करने के लिए इस उत्तर को बेहतर बनाने का प्रयास करूंगा। ;)
Ch3steR

@ Ch3steR अगर आप चाहें तो मैं इसे हिलाकर आपकी मदद कर सकता हूं। मैं सॉफ्टवेयर सत्यापन के क्षेत्र में एक पेशेवर हूं :)
जीन-फ्रेंकोइस टी।

@ जीन FrancoisT। मुझे अच्छा लगेगा। इसका मतलब बहुत होगा। मैं 3 साल का अंडरग्रेजुएट छात्र हूं।
Ch3steR

2
अच्छी नौकरी, यह मेरे द्वारा लिखे गए सभी परीक्षणों को पास करता है: repl.it/@jfthuong/MadPhysicistChester आप उन्हें ऑनलाइन चला सकते हैं, बहुत सुविधाजनक :)
जीन-फ्रेंकोइस टी।

6

मैं देख सकता हूं कि आपको यह उत्तर पहले से ही मिल गया है और यह टिप्पणियों में लाया गया है, लेकिन मुझे लगा कि मैं इसका उत्तर दूंगा। आप उपयोग करना चाहते हैं itertools.zip_longest(), जो छोटे जनरेटर के खाली मूल्यों को बदल देगा None:

import itertools

def my_gen(n:int):
    for i in range(n):
        yield i

gen1 = my_gen(10)
gen2 = my_gen(8)

for i, j in itertools.zip_longest(gen1, gen2):
    print(i, j)

प्रिंटों:

0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 None
9 None

तुम भी एक आपूर्ति कर सकते हैं fillvalueजब बुला तर्क zip_longestको बदलने के लिए Noneएक बार आप हिट एक अपने समाधान के लिए एक डिफ़ॉल्ट मान के साथ, लेकिन मूल रूप से None(या तो iया j) पाश के लिए में, अन्य चर अपने होगा 8


धन्यवाद। मैं वास्तव में पहले से ही आया हूं zip_longestऔर यह वास्तव में मेरे सवाल में था। :)
जीन-फ्रेंकोइस टी।

6

के GrandPhuba के आकर्षण से प्रेरित होकर zip, आइए एक "सुरक्षित" संस्करण बनाएं ( यहां पर परीक्षण किया गया है ):

def safe_zip(*args):
    """
    Safe zip that restores last consumed element in eachgenerator
    if not able to consume an element in all of them

    Returns:
        * generators in tuple
        * generator for zipped generators
    """
  continue_ = True
  n = len(args)
  result = (_ for _ in [])
  while continue_:
    addend = []
    for i, gen in enumerate(args):
      try:
        value = next(gen)
        addend.append(value)
      except StopIteration:
        genlist = list(args)
        args = tuple([chain([v], g) for v, g in zip(addend, genlist[:i])]+genlist[i:])
        continue_ = False
        break
    if len(addend)==n: result = chain(result, [tuple(addend)])
  return args, result

यहाँ एक मूल परीक्षण है:

    g1, g2 = (i for i in range(10)), (i for i in range(4))
    # Create (g1, g2), g3 first, then loop over g3 as one would with zip
    (g1, g2), g3 = safe_zip(g1, g2)
    for a, b in g3:
        print(a, b)#(0, 0) to (3, 3)
    for x in g1:
        print(x)#4 to 9

4

आप itertools.tee और itertools.islice का उपयोग कर सकते हैं :

from itertools import islice, tee

def zipped(gen1, gen2, pred=list):
    g11, g12 = tee(gen1)
    z = pred(zip(g11, gen2))

    return (islice(g12, len(z), None), gen2), z

gen1 = iter(range(10))
gen2 = iter(range(5))

(gen1, gen2), output = zipped(gen1, gen2)

print(output)
print(next(gen1))
# [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]
# 5

3

यदि आप कोड का पुन: उपयोग करना चाहते हैं, तो सबसे आसान उपाय है:

from more_itertools import peekable

a = peekable(a)
b = peekable(b)

while True:
    try:
        a.peek()
        b.peek()
    except StopIteration:
        break
    x = next(a)
    y = next(b)
    print(x, y)


print(list(a), list(b))  # Misses nothing.

आप अपने सेटअप का उपयोग करके इस कोड का परीक्षण कर सकते हैं:

def my_gen(n: int):
    yield from range(n)

a = my_gen(10)
b = my_gen(8)

यह प्रिंट होगा:

0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
[8, 9] []

2

मुझे नहीं लगता कि आप लूप के लिए बुनियादी के साथ गिराए गए मूल्य को पुनः प्राप्त कर सकते हैं, क्योंकि थकाऊ इटेरेटर, से लिया गया है zip(..., ...).__iter__ एक बार समाप्त होने के बाद छोड़ दिया जाता है और आप इसे एक्सेस नहीं कर सकते।

आपको अपने ज़िप को म्यूट करना चाहिए, फिर आप किसी हैक किए गए कोड के साथ गिराए गए आइटम की स्थिति प्राप्त कर सकते हैं)

z = zip(range(10), range(8))
for _ in iter(z.__next__, None):
    ...
_, (one, other) = z.__reduce__()
_, (i_one,), p_one = one.__reduce__() # p_one == current pos, 1 based
import itertools
val = next(itertools.islice(iter(i_one), p_one - 1, p_one))
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.