দ্বিতীয়টি সংক্ষিপ্ত হওয়ার সাথে জিপযুক্ত পাইথন জেনারেটর: কীভাবে নিঃশব্দে গ্রাস করা উপাদান পুনরুদ্ধার করবেন


50

আমি এর সাথে 2 টি জেনারেটর (সম্ভাব্য) বিভিন্ন দৈর্ঘ্যের পার্স করতে চাই zip:

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

তবে gen2কম উপাদান থাকলে এর একটি অতিরিক্ত উপাদান gen1হ'ল "গ্রাস" med

উদাহরণ স্বরূপ,

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 : আপনি নতুনভাবে প্রয়োগ এবং জমা দিতে চাইলে এই আরপিএলে বিভিন্ন বাস্তবায়নের কয়েকটি পরীক্ষা তৈরি করেছি :) https://repl.it/@jfthuong/MadPhysicistChester


19
দস্তাবেজগুলি লক্ষ্য করে যে "জিপ () কেবলমাত্র অসম দৈর্ঘ্যের ইনপুটগুলির সাথে ব্যবহার করা উচিত যখন আপনি লম্বা পুনরাবৃত্তের থেকে অতুলনীয় মানগুলির যত্ন নেবেন না those যদি এই মানগুলি গুরুত্বপূর্ণ হয় তবে পরিবর্তে itertools.zip_longest () ব্যবহার করুন।"
কারসিজেনিকেট

2
@ Ch3steR। তবে প্রশ্নটির "কেন" সাথে কোনও সম্পর্ক নেই। এটি আক্ষরিক অর্থে লেখা হয়েছে "এই হারানো মানটি পুনরুদ্ধার করার কোনও উপায় আছে কি ...?" দেখে মনে হচ্ছে সমস্ত উত্তর কিন্তু আমার সুবিধামত সেই অংশটি পড়তে ভুলে গেছেন।
ম্যাড পদার্থবিদ

@ ম্যাডফিসিসিস্ট সত্যিই অদ্ভুত। আমি সেই দিকটি পরিষ্কার করার জন্য প্রশ্নটি পুনরায় চাপিয়ে দিয়েছি।
জিন-ফ্রাঙ্কোইস টি।

1
মূল সমস্যাটি হ'ল জেনারেটরটিতে উঁকি মারার বা ধাক্কা দেওয়ার কোনও উপায় নেই। তাই একবার থেকে zip()পড়েছি , এটি চলে গেছে। 8gen1
বর্মার

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জিপটিতে বিভিন্ন ফলাফলের দিকে নিয়ে যায়) he চিয়ার্স
জ্যান-ফ্রাঙ্কোয়েস টি।

1
@ জাঁ ফ্রাঁসোয়া। আমি lastক্লান্ত হয়ে যাওয়ার পরে কলগুলিতে সঠিকভাবে প্রতিক্রিয়া জানাতে পুনরাবৃত্তিকে আপডেট করেছি । এটি আপনার শেষ মানটি দরকার কিনা তা নির্ধারণে সহায়তা করা উচিত। এটিকে আরও উত্পাদন-ওয় করে তোলে y
ম্যাড পদার্থবিদ

@MadPhysicist আমি কোড এবং আউটপুট দৌড়ে print(gen1.last) print(next(gen1)) হয়None and 9
Ch3steR

কিছু ম্যাডস্ট্রিং এবং সমস্ত সহ @ ম্যাডফিসিসিস্ট। ভাল;) আমি সময় পেলে পরে যাব সময় অতিবাহিত করার জন্য ধন্যবাদ
জিন-ফ্রাঙ্কোয়েস টি।

@ 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 ম পুনরুক্তি পর্যন্ত গ্রাস করা হয় পরে। এখন ৮ ম পুনরাবৃত্তিতে gen1কলগুলি elem = next(it, sentinel)যা 8 ফেরত আসে তবে যখন এটি gen2কল elem = next(it, sentinel)করে তখন এটি ফিরে আসে sentinel(কারণ gen2এটি শেষ হয়ে যায়) এবং if elem is sentinelসন্তুষ্ট হয় এবং ফাংশন রিটার্ন কার্যকর করে এবং থামিয়ে দেয়। এখন next(gen1)ফেরত 9।

আপনার 2 য় উদাহরণ gen1 = gen(8)এবং gen2 = gen(10)। উভয় জেনারেটর 7 ম পুনরুক্তি পর্যন্ত গ্রাস করা হয় পরে। এখন অষ্টম পুনরাবৃত্তির 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 ধন্যবাদ। আপনার ম্যাডফিজিসিস্ট সমাধানের পরিবর্তনের কয়েকটি সীমাবদ্ধতা রয়েছে: # 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। আমি ম্যাডফিসিসিস্ট উত্তর এবং উত্তর চিহ্নিত করব। ধন্যবাদ!
জিন-ফ্রাঙ্কোইস টি।

@ জাঁ ফ্রাংকো। হ্যাঁ রাজি। আপনার উত্তরটি উত্তর হিসাবে চিহ্নিত করা উচিত। এটির সীমাবদ্ধতা রয়েছে। আমি এই উত্তরটি উন্নত করার চেষ্টা করব সমস্ত ক্ষেত্রে মোকাবিলার জন্য। ;)
Ch3steR

@ Ch3steR আপনি চাইলে আমি এটি কাঁপতে আপনাকে সহায়তা করতে পারি। আমি সফটওয়্যার ভ্যালিডেশন এর :) ক্ষেত্রে একটি পেশাদারী আছি
জাঁ ফ্রাঁসোয়া টি

@ জাঁ ফ্রাংকো। আমি চাই। এটি একটি অনেক মানে হবে। আমি তৃতীয় বর্ষের আন্ডারগ্রাডের ছাত্র।
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

@ গ্র্যান্ডফুবার অনুগ্রহ দ্বারা অনুপ্রাণিত হয়ে 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))
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.