Реализовать итератор для строк в Python - PullRequest
7 голосов
/ 23 июня 2011

Я делаю какой-то разбор, для которого требуется один токен lookahead.Что мне нужно, так это быстрая функция (или класс?), Которая бы брала итератор и превращала его в список кортежей в форме (token, lookahead), такой что:

>>> a = ['a', 'b', 'c', 'd']
>>> list(lookahead(a))
[('a', 'b'), ('b', 'c'), ('c', 'd'), ('d', None)]

в основном,это было бы удобно для заглядывания вперед в итераторах, подобных этому:

for (token, lookahead_1) in lookahead(a):
  pass

Хотя я не уверен, есть ли название для этого метода или функции в itertools, которые уже будут это делать.Есть идеи?

Спасибо!

Ответы [ 5 ]

11 голосов
/ 23 июня 2011

Существуют более простые способы, если вы просто используете списки - см. Ответ Свена. Вот один из способов сделать это для общих итераторов

>>> from itertools import tee, izip_longest
>>> a = ['a', 'b', 'c', 'd']
>>> it1, it2 = tee(iter(a))
>>> next(it2)  # discard this first value
'a'
>>> [(x,y) for x,y in izip_longest(it1, it2)]
    # or just list(izip_longest(it1, it2))
[('a', 'b'), ('b', 'c'), ('c', 'd'), ('d', None)]

Вот как использовать его в цикле for, как в вашем вопросе.

>>> it1,it2 = tee(iter(a))
>>> next(it2)
'a'
>>> for (token, lookahead_1) in izip_longest(it1,it2):
...     print token, lookahead_1
... 
a b
b c
c d
d None

Наконец, вот функция, которую вы ищете

>>> def lookahead(it):
...     it1, it2 = tee(iter(it))
...     next(it2)
...     return izip_longest(it1, it2)
... 
>>> for (token, lookahead_1) in lookahead(a):
...     print token, lookahead_1
... 
a b
b c
c d
d None
8 голосов
/ 23 июня 2011

Мне нравятся ответы Свена и Гнибблера , но почему-то мне нравится запускать собственный генератор.

def lookahead(iterable, null_item=None):
    iterator = iter(iterable) # in case a list is passed
    prev = iterator.next()
    for item in iterator:
        yield prev, item
        prev = item
    yield prev, null_item

Протестировано:

>>> for i in lookahead(x for x in []):
...     print i
... 
>>> for i in lookahead(x for x in [0]):
...     print i
... 
(0, None)
>>> for i in lookahead(x for x in [0, 1, 2]):
...     print i
... 
(0, 1)
(1, 2)
(2, None)

Edit : Карл и ninjagecko поднимают превосходную точку - переданная последовательность может содержать None, и поэтому использование None в качестве окончательного значения прогнозирующего значения может привести к неоднозначности. Но нет очевидной альтернативы; константа уровня модуля, возможно, является лучшим подходом во многих случаях, но может быть излишней для одноразовой функции, подобной этой - не говоря уже о том, что bool(object()) == True, что может привести к неожиданному поведению. Вместо этого я добавил параметр null_item со значением по умолчанию None - таким образом пользователи могут передавать все, что имеет смысл для их нужд, будь то простой object() страж, константа их собственного создания или даже экземпляр класса со специальным поведением. Поскольку большую часть времени None является очевидным и даже, возможно, ожидаемым поведением, я оставил None по умолчанию.

6 голосов
/ 23 июня 2011

Обычный способ сделать это для списка a -

from itertools import izip_longest
for token, lookahead in izip_longest(a, a[1:]):
    pass

За последний токен вы получите None в качестве прогнозного токена.

Если вы хотите избежать копирования списка, введенного a[1:], вы можете использовать islice(a, 1, None) вместо этого. Небольшая модификация, работающая для произвольных итераций, приведена в ответе от gnibbler . Для простой, легкой для понимания функции генератора, также работающей для произвольных итераций, см. ответ от senderle .

2 голосов
/ 23 июня 2011

Вы можете найти ответ на свой вопрос здесь: Использование Lookahead с генераторами .

1 голос
/ 23 июня 2011

Я считаю все эти ответы неправильными, потому что они приведут к непредвиденным ошибкам, если ваш список содержит None. Вот мой вариант:

SEQUENCE_END = object()

def lookahead(iterable):
    iter = iter(iterable)
    current = next(iter)
    for ahead in iter:
        yield current,ahead
        current = ahead
    yield current,SEQUENCE_END

Пример:

>>> for x,ahead in lookahead(range(3)):
>>>     print(x,ahead)
0, 1
1, 2
2, <object SEQUENCE_END>

Пример того, как этот ответ лучше:

def containsDoubleElements(seq):
    """
        Returns whether seq contains double elements, e.g. [1,2,2,3]
    """
    return any(val==nextVal for val,nextVal in lookahead(seq))

>>> containsDoubleElements([None])
False  # correct!

def containsDoubleElements_BAD(seq):
    """
        Returns whether seq contains double elements, e.g. [1,2,2,3]
    """
    return any(val==nextVal for val,nextVal in lookahead_OTHERANSWERS(seq))

>>> containsDoubleElements([None])
True  # incorrect!
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...