【问题标题】:Sorting an iterator in python在python中对迭代器进行排序
【发布时间】:2022-01-08 15:25:49
【问题描述】:

我想迭代一个大的itertoolsproduct,但我想以与product 提供的顺序不同的顺序进行。问题是使用sorted 对迭代器进行排序需要时间。例如:

from itertools import product
import time

RNG = 15
RPT = 6

start = time.time()
a = sorted(product(range(RNG), repeat=RPT), key=sum)
print("Sorted: " + str(time.time() - start))
print(type(a))

start = time.time()
a = product(range(RNG), repeat=RPT)
print("Unsorted: " + str(time.time() - start))
print(type(a))

创建排序迭代器大约需要两倍的时间。我猜这是因为sorted 实际上涉及遍历整个迭代器并返回一个列表。而第二个未排序的迭代器正在做某种惰性求值魔法。

我想这里真的有两个问题。

  1. 一般问题:是否有惰性求值方法来更改出现在迭代器中的订单项?
  2. 具体问题:有没有办法遍历所有小于n 的整数的m-length 列表,首先点击总和较小的列表?

【问题讨论】:

  • 你必须实现你自己的product 类/函数,它会按照你想要的顺序进行迭代。 product 是懒惰的,如果不先将它们全部读入内存,就无法对它产生的值进行排序。
  • @chepner 我担心这可能是答案。所以一般的问题,答案是“不”。答案的具体问题是“可能,但你必须自己动手”?
  • docs.python.org/3/library/itertools.html#itertools.product 有一些样板代码,如果您只想在 yield 之前对结果进行排序以获得自己的方法

标签: python python-3.x iterator


【解决方案1】:

如果您的目标是减少内存消耗,您可以编写自己的生成器以按总和的顺序返回排列(见下文)。但是,如果内存不是问题,对itertools.product() 的输出进行排序将比产生相同结果的 Python 代码更快。

可以通过基于最小和合并多个迭代器(每个起始值一个)来编写一个递归函数,该函数按总和的顺序生成值组合:

def sumCombo(A,N):
    if N==1:
        yield from ((n,) for n in A) # single item combos
        return
    pA = []                          # list of iterator/states
    for i,n in enumerate(A):         # for each starting value 
        ip = sumCombo(A[i:],N-1)     # iterator recursion to N-1
        p  = next(ip)                # current N-1 combination
        pA.append((n+sum(p),p,n,ip)) # sum, state & iterator
    while pA:
        # index and states of smallest sum
        i,(s,p,n,ip) = min(enumerate(pA),key=lambda ip:ip[1][0])
        ps = s
        while s == ps:        # output equal sum combinations
           yield (n,*p)       # yield starting number with recursed
           p = next(ip,None)  # advance iterator
           if p is None:
               del pA[i]      # remove exhausted iterators
               break
           s = n+sum(p)       # compute new sum
           pA[i] = (s,p,n,ip) # and update states

这只会产生值的组合,而不是产生这些组合的不同排列的产品。 (38,760 种组合与 11,390,625 种产品)。

为了获得所有产品,您需要通过生成不同排列的函数运行这些组合:

def permuteDistinct(A):
    if len(A) == 1:
        yield tuple(A) # single value
        return
    seen = set()               # track starting value
    for i,n in enumerate(A):   # for each starting value
        if n in seen: continue # not yet used
        seen.add(n)
        for p in permuteDistinct(A[:i]+A[i+1:]): 
            yield (n,*p)       # starting value & rest

def sumProd(A,N):     
    for p in sumCombo(A,N):           # combinations in order of sum
        yield from permuteDistinct(p) # permuted

所以sumProd(range(RNG),RPT) 将按总和的顺序生成 11,390,625 个排列,而不将它们存储在列表中,但这样做需要 5 倍的时间(与对产品进行排序相比)。

a = sorted(product(range(RNG), repeat=RPT), key=sum) # 4.6 sec
b = list(sumProd(range(RNG),RPT))                    # 23  sec

list(map(sum,a)) == list(map(sum,b)) # True  (same order of sums)
a == b                               # False (order differs for equal sums)

a[5:15]            b[5:15]             sum
(0, 1, 0, 0, 0, 0) (0, 1, 0, 0, 0, 0)  1
(1, 0, 0, 0, 0, 0) (1, 0, 0, 0, 0, 0)  1
(0, 0, 0, 0, 0, 2) (0, 0, 0, 0, 0, 2)  2
(0, 0, 0, 0, 1, 1) (0, 0, 0, 0, 2, 0)  2
(0, 0, 0, 0, 2, 0) (0, 0, 0, 2, 0, 0)  2
(0, 0, 0, 1, 0, 1) (0, 0, 2, 0, 0, 0)  2
(0, 0, 0, 1, 1, 0) (0, 2, 0, 0, 0, 0)  2
(0, 0, 0, 2, 0, 0) (2, 0, 0, 0, 0, 0)  2
(0, 0, 1, 0, 0, 1) (0, 0, 0, 0, 1, 1)  2
(0, 0, 1, 0, 1, 0) (0, 0, 0, 1, 0, 1)  2

如果您的流程正在搜索特定的总和,那么首先过滤组合并仅扩展满足您条件的组合(总和)的不同排列可能会很有趣。这可能会大大减少迭代次数(sumCombo(range(RNG),RPT) # 0.22 sec 比对产品进行排序更快)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-15
    • 2016-08-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多