web-dev-qa-db-ja.com

Pythonジェネレーターで1つの要素(ピーク)を先読みする方法は?

Pythonジェネレーターで1つの要素を先読みする方法がわかりません。見たらすぐに消えてしまいます。

ここに私が意味するものがあります:

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())

誰かが私があなたが1つの要素を先読みできるジェネレータを書くのを手伝ってもらえますか?

64
bodacydo

PythonジェネレーターAPIは1つの方法です。読んだ要素をプッシュバックすることはできません。ただし、 itertoolsモジュール を使用して新しいイテレーターを作成し、素子:

import itertools

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

完全を期すために、 more-itertools package (おそらくPythonプログラマーのツールボックスの一部であるはずです)この動作を実装するpeekableラッパーが含まれます。 のコード例としてドキュメント ショー:

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

このパッケージは、ドキュメントにPython 2構文が示されていても、Python 2と3の両方と互換性があります。

67
David Z

わかりました-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

つまり、反復中はいつでもリスト内の次の項目にアクセスできます。

25
plof

Itertools.teeを使用して、ジェネレーターの軽量コピーを作成できます。次に、1つのコピーを先読みしても、2番目のコピーには影響しません。

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)

「アイテム」ジェネレーターは、「ピーカー」をいじくり回しても影響を受けません。 'tee'を呼び出した後、元の 'seq'を使用するべきではないことに注意してください。

FWIW、これはこの問題を解決するwrongの方法です。ジェネレーターで1つ先の項目を探す必要があるアルゴリズムは、現在のジェネレーター項目と前の項目を使用するように書くこともできます。そうすれば、ジェネレーターの使用を混乱させる必要がなくなり、コードがはるかに簡単になります。この質問に対する他の答えをご覧ください。

16
>>> 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]
5
robert king

楽しみのために、アーロンの提案に基づいて先読みクラスの実装を作成しました。

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

使用方法は上記と同じですが、ここで何度もピークを使用するために価格を支払うことはありません。さらに数行追加することで、イテレータ内の複数のアイテムを先読みできます(使用可能なRAMまで)。

5
Bluehorn

アイテム(i、i + 1)を使用する代わりに、「i」は現在のアイテムであり、i + 1は「先読み」バージョンです。(i-1、i)を使用する必要があります。ジェネレータからの以前のバージョンです。

この方法でアルゴリズムを調整すると、「先読み」しようとする余分な不必要な複雑さを除けば、現在持っているものと同じものが生成されます。

先を覗くのは間違いであり、あなたはそれをしてはいけません。

4

これは機能します-アイテムをバッファリングし、各アイテムとシーケンス内の次のアイテムで関数を呼び出します。

シーケンスの最後に何が起こるかについての要件は不透明です。最後にいるとき、「先読み」とはどういう意味ですか?

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
3
S.Lott

簡単な解決策は、次のような関数を使用することです。

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
3
Thomas Ahle

誰かが興味を持っているなら、私が間違っているなら私を修正してください。しかし、イテレータにプッシュバック機能を追加するのはかなり簡単だと思います。

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
2
nilo

ここではitertools.chain()がジョブの自然なツールですが、次のようなループに注意してください。

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

...これは線形的に増加するメモリ量を消費し、最終的には停止するためです。 (このコードは基本的にchain()呼び出しごとに1つのノードのリンクリストを作成するようです。)これは、ライブラリを検査したからではなく、プログラムの大幅なスローダウンを引き起こしたためです-gen = itertools.chain([peek], gen)行はそれを再び高速化しました。 (Python 3.3)

1
Jacob Eliosoff

w.r.t @David 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
1
pylang

@ jonathan-hartley answerの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を何らかの属性に配置するクラスを作成するのは簡単です。

1
nitely

cytoolz には peek 機能があります。

>> from cytoolz import peek
>> gen = iter([1,2,3])
>> first, continuation = peek(gen)
>> first
1
>> list(continuation)
[1, 2, 3]
1
W.P. McNeill