كيف نتطلع إلى الأمام عنصر واحد (نظرة خاطفة) في مولد الثعبان؟

StackOverflow https://stackoverflow.com/questions/2425270

  •  19-09-2019
  •  | 
  •  

سؤال

لا أستطيع معرفة كيفية البحث عن عنصر واحد في مولد الثعبان. بمجرد أن أنظر انه ذهب.

هنا ما أعنيه:

gen = iter([1,2,3])
next_value = gen.next()  # okay, I looked forward and see that next_value = 1
# but now:
list(gen)  # is [2, 3]  -- the first value is gone!

إليك مثال أكثر واقعية:

gen = element_generator()
if gen.next_value() == 'STOP':
  quit_application()
else:
  process(gen.next())

هل يمكن لأي شخص أن يساعدني في كتابة مولد يمكنك أن تنظر إلى عنصر واحد للأمام؟

هل كانت مفيدة؟

المحلول

API Generator Python هو إحدى الطرق: لا يمكنك دفع العناصر التي قرأتها. ولكن يمكنك إنشاء معطف جديد باستخدام وحدة itertools. وإعداد العنصر:

import itertools

gen = iter([1,2,3])
peek = gen.next()
print list(itertools.chain([peek], gen))

نصائح أخرى

من أجل الاكتمال، more-itertools صفقة (والتي يجب أن تكون جزءا من أي مجموعة أدوات Python Programmer) تتضمن أ peekable المجمع الذي ينفذ هذا السلوك. كما مثال الرمز في وثائق عروض:

>>> p = peekable(xrange(2))
>>> p.peek()
0
>>> p.next()
0
>>> p.peek()
1
>>> p.next()
1

تعتبر الحزمة متوافقة مع كل من Python 2 و 3، على الرغم من أن الوثائق تظهر بناء جملة Python 2.

حسنا - بعد عامين متأخرين جدا - لكنني صادفت هذا السؤال، ولم أجد أي من الإجابات على رضاني. جاء مع مولد الويتا هذا:

class Peekorator(object):

    def __init__(self, generator):
        self.empty = False
        self.peek = None
        self.generator = generator
        try:
            self.peek = self.generator.next()
        except StopIteration:
            self.empty = True

    def __iter__(self):
        return self

    def next(self):
        """
        Return the self.peek element, or raise StopIteration
        if empty
        """
        if self.empty:
            raise StopIteration()
        to_return = self.peek
        try:
            self.peek = self.generator.next()
        except StopIteration:
            self.peek = None
            self.empty = True
        return to_return

def simple_iterator():
    for x in range(10):
        yield x*3

pkr = Peekorator(simple_iterator())
for i in pkr:
    print i, pkr.peek, pkr.empty

النتائج في:

0 3 False
3 6 False
6 9 False
9 12 False    
...
24 27 False
27 None False

أي لديك في أي لحظة أثناء الوصول إلى العنصر التالي في القائمة.

يمكنك استخدام itertools.tee لإنتاج نسخة خفيفة الوزن من المولدات. ثم تطل في المقدمة في نسخة واحدة لن تؤثر على النسخة الثانية:

import itertools

def process(seq):
    peeker, items = itertools.tee(seq)

    # initial peek ahead
    # so that peeker is one ahead of items
    if next(peeker) == 'STOP':
        return

    for item in items:

        # peek ahead
        if next(peeker) == "STOP":
            return

        # process items
        print(item)

لا يتأثر مولد "البنود" بمهمة "peeker". لاحظ أنه يجب ألا تستخدم "SEQ" الأصلي بعد استدعاء "Tee" على ذلك، من شأنه أن يكسر الأشياء.

FWIW، هذا هو خاطئ - ظلم - يظلم طريقة لحل هذه المشكلة. أي خوارزمية تتطلب منك البحث عن عنصر واحد قدما في مولد قد تكون مكتوبة بديلة لاستخدام عنصر المولد الحالي والبند السابق. ثم لا تضطر إلى مغلف استخدامك للمولدات وسوف يكون رمزك أكثر بساطة. انظر إجابتي الأخرى على هذا السؤال.

>>> gen = iter(range(10))
>>> peek = next(gen)
>>> peek
0
>>> gen = (value for g in ([peek], gen) for value in g)
>>> list(gen)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

للمتعة فقط، قمت بإنشاء تنفيذ فئة مظهره المجند بناء على اقتراح هارون:

import itertools

class lookahead_chain(object):
    def __init__(self, it):
        self._it = iter(it)

    def __iter__(self):
        return self

    def next(self):
        return next(self._it)

    def peek(self, default=None, _chain=itertools.chain):
        it = self._it
        try:
            v = self._it.next()
            self._it = _chain((v,), it)
            return v
        except StopIteration:
            return default

lookahead = lookahead_chain

مع هذا، سيعمل ما يلي:

>>> t = lookahead(xrange(8))
>>> list(itertools.islice(t, 3))
[0, 1, 2]
>>> t.peek()
3
>>> list(itertools.islice(t, 3))
[3, 4, 5]

مع هذا التنفيذ، فهو فكرة سيئة لاستدعاء نظرة خاطفة عدة مرات على التوالي ...

أثناء النظر إلى شفرة CPYTHON المصدر، وجدت طريقة أفضل وهي أقصر وأكثر كفاءة:

class lookahead_tee(object):
    def __init__(self, it):
        self._it, = itertools.tee(it, 1)

    def __iter__(self):
        return self._it

    def peek(self, default=None):
        try:
            return self._it.__copy__().next()
        except StopIteration:
            return default

lookahead = lookahead_tee

الاستخدام هو نفسه أعلاه ولكن لن تدفع سعرا هنا لاستخدام نظرة خاطفة عديدة على التوالي. مع عدد قليل من الخطوط التي يمكنك أيضا أن تنظر إلى الأمام أكثر من عنصر واحد في جهاز الكمبيوتر الخاص بالمكترولمة (حتى ذاكرة الوصول العشوائي المتاحة).

بدلا من استخدام العناصر (I، I + 1)، حيث "أنا" هو العنصر الحالي و I + 1 هو الإصدار "نظرة خاطفة"، يجب أن تستخدم (I-1، I)، حيث "I-1" هو الإصدار السابق من المولد.

إن التغيير والتبديل الخوارزمية الخاصة بك هذه الطريقة سوف تنتج شيئا مطابقا لما لديك حاليا، بصرف النظر عن التعقيد الإضافي الذي لا داعي له في محاولة "نظرة خاطفة".

تطل في المستقبل خطأ، ويجب ألا تفعل ذلك.

سيعمل هذا - تقوم بمعالجها عنصر ويستدعي وظيفة مع كل عنصر والبند التالي في التسلسل.

الاحتياجات الخاصة بك غامضة على ما يحدث في نهاية التسلسل. ماذا يعني "أنظر إلى الأمام" عندما تكون في آخر واحد؟

def process_with_lookahead( iterable, aFunction ):
    prev= iterable.next()
    for item in iterable:
        aFunction( prev, item )
        prev= item
    aFunction( item, None )

def someLookaheadFunction( item, next_item ):
    print item, next_item

الحل البسيط هو استخدام وظيفة مثل هذا:

def peek(it):
    first = next(it)
    return first, itertools.chain([first], it)

ثم يمكنك القيام به:

>>> it = iter(range(10))
>>> x, it = peek(it)
>>> x
0
>>> next(it)
0
>>> next(it)
1

إذا كان أي شخص مهتم، فالرجاء تصحيح لي إذا كنت مخطئا، لكنني أعتقد أنه من السهل جدا إضافة بعض وظائف الدفع إلى أي جهاز كمتفيذ.

class Back_pushable_iterator:
    """Class whose constructor takes an iterator as its only parameter, and
    returns an iterator that behaves in the same way, with added push back
    functionality.

    The idea is to be able to push back elements that need to be retrieved once
    more with the iterator semantics. This is particularly useful to implement
    LL(k) parsers that need k tokens of lookahead. Lookahead or push back is
    really a matter of perspective. The pushing back strategy allows a clean
    parser implementation based on recursive parser functions.

    The invoker of this class takes care of storing the elements that should be
    pushed back. A consequence of this is that any elements can be "pushed
    back", even elements that have never been retrieved from the iterator.
    The elements that are pushed back are then retrieved through the iterator
    interface in a LIFO-manner (as should logically be expected).

    This class works for any iterator but is especially meaningful for a
    generator iterator, which offers no obvious push back ability.

    In the LL(k) case mentioned above, the tokenizer can be implemented by a
    standard generator function (clean and simple), that is completed by this
    class for the needs of the actual parser.
    """
    def __init__(self, iterator):
        self.iterator = iterator
        self.pushed_back = []

    def __iter__(self):
        return self

    def __next__(self):
        if self.pushed_back:
            return self.pushed_back.pop()
        else:
            return next(self.iterator)

    def push_back(self, element):
        self.pushed_back.append(element)
it = Back_pushable_iterator(x for x in range(10))

x = next(it) # 0
print(x)
it.push_back(x)
x = next(it) # 0
print(x)
x = next(it) # 1
print(x)
x = next(it) # 2
y = next(it) # 3
print(x)
print(y)
it.push_back(y)
it.push_back(x)
x = next(it) # 2
y = next(it) # 3
print(x)
print(y)

for x in it:
    print(x) # 4-9

برغم من itertools.chain() هي الأداة الطبيعية للمهمة هنا، حذار من الحلقات مثل هذا:

for elem in gen:
    ...
    peek = next(gen)
    gen = itertools.chain([peek], gen)

... لأن هذا سوف يستهلك كمية خطية متزايدة من الذاكرة، وتطعن في نهاية المطاف إلى توقف. (يبدو أن هذا الكود بشكل أساسي لإنشاء قائمة مرتبطة، عقدة واحدة لكل سلسلة ().) أعلم أن هذا ليس لأنني تفقد Libs ولكن لأن هذا أدى فقط إلى تباطؤ رئيسي لبرنامجي - التخلص من gen = itertools.chain([peek], gen) خط استفصله مرة أخرى. (بيثون 3.3)

مقتطف Python3 ل @ جوناثان هارتلي إجابه:

def peek(iterator, eoi=None):
    iterator = iter(iterator)

    try:
        prev = next(iterator)
    except StopIteration:
        return iterator

    for elm in iterator:
        yield prev, elm
        prev = elm

    yield prev, eoi


for curr, nxt in peek(range(10)):
    print((curr, nxt))

# (0, 1)
# (1, 2)
# (2, 3)
# (3, 4)
# (4, 5)
# (5, 6)
# (6, 7)
# (7, 8)
# (8, 9)
# (9, None)

سيكون الأمر واضحا لإنشاء فصل يفعل ذلك __iter__ ونتائج فقط prev البند ووضع elm في بعض السمة.

WRT Bavid Z مشاركة، الأحدث seekable يمكن للأداة إعادة تعيين مكرمة ملفوفة إلى موضع مسبق.

>>> s = mit.seekable(range(3))
>>> s.next()
# 0

>>> s.seek(0)                                              # reset iterator
>>> s.next()
# 0

>>> s.next()
# 1

>>> s.seek(1)
>>> s.next()
# 1

>>> next(s)
# 2

cytoolz. لديه أ نظرة خاطفة وظيفة.

>> from cytoolz import peek
>> gen = iter([1,2,3])
>> first, continuation = peek(gen)
>> first
1
>> list(continuation)
[1, 2, 3]
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top