【问题标题】:Elegant way to skip elements in an iterable跳过可迭代元素的优雅方式
【发布时间】:2013-05-23 22:37:31
【问题描述】:

我有一个大的迭代器,事实上,一个大的迭代器由以下给出:

itertools.permutations(range(10))

我想访问第百万个元素。我已经用一些不同的方式解决了问题。

  1. 将可迭代对象转换为列表并获取第 1000000 个元素:

    return list(permutations(range(10)))[999999]
    
  2. 手动跳过元素直到 999999:

    p = permutations(range(10))
    for i in xrange(999999): p.next()
    return p.next()
    
  3. 手动跳过元素 v2:

    p = permutations(range(10))
    for i, element in enumerate(p):
        if i == 999999:
            return element
    
  4. 使用来自 itertools 的 islice:

    return islice(permutations(range(10)), 999999, 1000000).next()
    

但我仍然不觉得它们都不是 python 的优雅方式。第一个选项太昂贵了,它需要计算整个迭代来访问单个元素。如果我没记错的话,islice 在内部进行的计算与我在方法 2 中所做的相同,并且几乎与第 3 次一样,也许它有更多的冗余操作。

所以,我只是好奇,想知道在 python 中是否有其他方式可以访问可迭代的具体元素,或者至少以更优雅的方式跳过第一个元素,或者我是否只需要使用上述之一。

【问题讨论】:

    标签: python iterator iterable


    【解决方案1】:

    使用itertools recipe consume 跳过n 元素:

    def consume(iterator, n):
        "Advance the iterator n-steps ahead. If n is none, consume entirely."
        # Use functions that consume iterators at C speed.
        if n is None:
            # feed the entire iterator into a zero-length deque
            collections.deque(iterator, maxlen=0)
        else:
            # advance to the empty slice starting at position n
            next(islice(iterator, n, n), None)
    

    注意那里的islice() 调用;它使用n, n,实际上不返回任何东西next() 函数回退到默认值。

    简化为您的示例,您希望跳过 999999 个元素,然后返回元素 1000000:

    return next(islice(permutations(range(10)), 999999, 1000000))
    

    islice() 在 C 中处理迭代器,这是 Python 循环无法击败的。

    为了说明,以下是每种方法仅重复 10 次的时间:

    >>> from itertools import islice, permutations
    >>> from timeit import timeit
    >>> def list_index():
    ...     return list(permutations(range(10)))[999999]
    ... 
    >>> def for_loop():
    ...     p = permutations(range(10))
    ...     for i in xrange(999999): p.next()
    ...     return p.next()
    ... 
    >>> def enumerate_loop():
    ...     p = permutations(range(10))
    ...     for i, element in enumerate(p):
    ...         if i == 999999:
    ...             return element
    ... 
    >>> def islice_next():
    ...     return next(islice(permutations(range(10)), 999999, 1000000))
    ... 
    >>> timeit('f()', 'from __main__ import list_index as f', number=10)
    5.550895929336548
    >>> timeit('f()', 'from __main__ import for_loop as f', number=10)
    1.6166789531707764
    >>> timeit('f()', 'from __main__ import enumerate_loop as f', number=10)
    1.2498459815979004
    >>> timeit('f()', 'from __main__ import islice_next as f', number=10)
    0.18969106674194336
    

    islice() 方法比第二快的方法快近 7 倍。

    【讨论】:

    • 那是快速且非常详细的好答案。顺便说一句,你还教了我一种使用 timeit 调用函数的方法。谢谢=D
    【解决方案2】:

    查找第 n 个排列可能只是一个示例,但如果这实际上是您要解决的问题,那么有一个更好的方法可以做到这一点。您可以直接计算第 n 个排列,而不是跳过可迭代的元素。借用another answer here的代码:

    import math
    
    def nthperm(li, n):
        li = list(li)
        n -= 1
        s = len(li)
        res = []
        if math.factorial(s) <= n:
            return None
        for x in range(s-1,-1,-1):
            f = math.factorial(x)
            d = n / f
            n -= d * f
            res.append(li[d])
            del(li[d])
        return res
    

    示例和时序对比:

    In [4]: nthperm(range(10), 1000000)
    Out[4]: [2, 7, 8, 3, 9, 1, 5, 4, 6, 0]
    
    In [5]: next(islice(permutations(range(10)), 999999, 1000000))
    Out[5]: (2, 7, 8, 3, 9, 1, 5, 4, 6, 0)
    
    In [6]: %timeit nthperm(range(10), 1000000)
    100000 loops, best of 3: 9.01 us per loop
    
    In [7]: %timeit next(islice(permutations(range(10)), 999999, 1000000))
    10 loops, best of 3: 29.5 ms per loop
    

    同样的答案,速度快了 3000 多倍。请注意,我确实对原始代码进行了轻微修改,使其不再破坏原始列表。

    【讨论】:

    • 这不是问题的上限,我只是对从迭代中跳过元素的更快方法感到好奇。但是,您的答案是更快地解决第 n 个阶乘的有趣方法。我给了你+1。谢谢!
    【解决方案3】:

    为了获得下一个而吃掉一百万件物品确实是非常浪费的。不幸的是,是否可以避免取决于您的迭代器:如果迭代器有办法直接跳转到特定的偏移量,它可以实现__getitem__ 方法,您可以使用它直接请求iterator[1000000]。 (它如何到达那里取决于生成算法)。

    如果您的数据源需要生成所有先前的值才能到达那里,那么如何丢弃它们是您最不关心的问题。您可以选择一个不错的方式,但这只是锦上添花。

    附言。鉴于您的问题的上下文,我将概述一种用于直接生成第 n 个排列的算法,但我看到@F.J.打败我。不错的解决方案! :-)

    【讨论】:

    • 许多进程无法为您提供该选项;例如网络套接字。或具有可变长度行的文件,需要跳过 x 行。但是,是的,如果您可以直接“寻找”您需要的项目,那么请使用它。
    • 没错,这就是我想要达到的目的。什么是“不优雅”(阅读:低效)是通常无法摆脱生成跳过的项目。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-25
    • 2013-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多