【问题标题】:Implement lookahead iterator for strings in Python在 Python 中为字符串实现前瞻迭代器
【发布时间】:2011-09-20 20:16:45
【问题描述】:

我正在做一些需要一个前瞻标记的解析。我想要的是一个快速函数(或类?),它会接受一个迭代器并将其转换为表单中的元组列表(令牌,前瞻),这样:

>>> 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 中是否有该技术或函数的名称已经可以做到这一点。有什么想法吗?

谢谢!

【问题讨论】:

标签: python algorithm


【解决方案1】:

如果您只使用列表,还有更简单的方法 - 请参阅 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

【讨论】:

  • 除非SEQUENCE_END = object()izip_longest(..., fillvalue=SEQUENCE_END),否则我会谨慎使用它,因为序列本身可能包含None(默认填充值)。
  • @ninjagecko,OP 在问题中使用 None ,很高兴这是默认的填充值。我不确定我是否理解您的观点 - 当迭代器用完时,for 循环终止,序列中的其他 Nones 没有问题。唯一需要一个不同值的情况是,如果出于某种原因需要对最终迭代进行特殊处理
  • @gnibbler:考虑使用lookahead 来确定序列是否包含双重复值的用例,例如写一个函数doesSequenceContainDoubleValue(sequence)。如果一个人在没有特殊SEQUENCE_END 对象的情况下使用这个实现,如果一个人只说any(x==nextX for x,nextX in lookahead(...)),那将是一个极其悲惨的编码错误,这是应该能够做到的。
  • @ninjagecko:当然,在这种情况下(希望很明显)需要有一个不同的填充值,但是在 final 序列的值也是None.
  • @gnibbler:一个小的简化是使用tee(it)而不是tee(iter(it))——这些对于所有可迭代的it都是等价的。
【解决方案2】:

我喜欢 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 的事实,这可能导致意外行为。相反,我添加了一个默认为 Nonenull_item 参数——这样用户可以传入任何对他们的需求有意义的参数,无论是简单的 object() 哨兵,还是他们自己创建的常量,或者甚至是具有特殊行为的类实例。因为大多数时候None 是显而易见的,甚至可能是预期的行为,所以我将None 作为默认设置。

【讨论】:

  • 我最后一次只是yield prev,。这样你就可以告诉你你在最后,因为只有一个项目。毕竟,您的迭代可能在序列中包含None。或者至少,使用一个特殊的对象来指示序列的结束。
  • @Karl,有趣的一点——但yield prev, 不会导致解包中断吗?一个特殊的对象可以工作...
  • 确实如此,所以这绝对是添加一些东西的重点。
  • +1 我继续写了一个答案,并意识到我的代码是 100% 相同的。几乎 100%... 我会做 SEQUENCE_END = object() 并在最后返回它而不是 None;这在序列可能包含None 的情况下非常重要。编辑:@Karl:该死,今晚每个人都是正确的
  • @Karl,@ninjagecko,好点——我决定添加一个 null_item 参数,而不是硬编码它。
【解决方案3】:

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

【讨论】:

  • OP 要求在迭代器上执行此操作,但这假设是一个列表。
  • 除非SEQUENCE_END = object()izip_longest(..., fillvalue=SEQUENCE_END),否则我也会谨慎使用它,因为序列本身可能包含None(默认填充值)。
  • @Nick 可以使用itertools.tee修改它以在不只是列表上工作
  • @ninjagecko 确实可以 - 我指出您的回答并没有解决 OP 提出的问题。
【解决方案4】:

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

【讨论】:

    【解决方案5】:

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

    【讨论】:

    • 这个答案显然是错误,因为问题中给出的示例是用None填充的。我明白你的意思,但你回答错了问题
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-15
    • 1970-01-01
    • 2012-10-23
    • 2017-03-27
    • 1970-01-01
    • 2021-04-03
    相关资源
    最近更新 更多