在Python中为字符串实现lookahead迭代器

时间:2011-06-23 00:30:48

标签: python algorithm

我正在做一些需要一个前瞻标记的解析。我想要的是一个快速函数(或类?),它将采用迭代器并将其转换为表单中的元组列表(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 个答案:

答案 0 :(得分:11)

如果您只是使用列表,有更简单的方法 - 请参阅Sven的回答。这是为通用迭代器执行此操作的一种方法

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

答案 1 :(得分:8)

我同时喜欢Sven'sgnibbler's个答案,但出于某种原因,我喜欢推出自己的发电机。

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)

编辑:Karl和ninjagecko提出了一个很好的观点 - 传入的序列可能包含None,因此使用None作为最终前瞻值可能会导致歧义。但是没有明显的替代方案;在许多情况下,模块级常量可能是最好的方法,但对于像这样的一次性函数可能有点过分 - 更不用说bool(object()) == True这可能导致意外行为的事实。相反,我添加了一个null_item参数,默认值为None - 这样用户就可以传入任何有意义的参数,无论是一个简单的object()哨兵,一个常数他们自己的创造,甚至是具有特殊行为的类实例。由于大部分时间None是明显的,甚至可能是预期的行为,因此我将None作为默认值。

答案 2 :(得分:6)

对列表a执行此操作的常用方法是

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

对于最后一个标记,您将获得None作为预告标记。

如果您想避免a[1:]引入的列表副本,可以改用islice(a, 1, None)。对于任意迭代的略微修改,请参阅answer by gnibbler。对于一个简单易用的生成器函数,也适用于任意迭代,请参阅the answer by senderle

答案 3 :(得分:2)

您可以在此处找到问题的答案:Using lookahead with generators

答案 4 :(得分:1)

我认为所有这些答案都不正确,因为如果您的列表包含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!