为什么Python';s itertools.排列是否包含重复项?(当原始列表有重复项时)
人们普遍认为n个不同符号的列表有n个!排列。然而,当符号不明显时,在数学和其他领域,最常见的惯例似乎是只计算不同的排列。因此,列表为什么Python';s itertools.排列是否包含重复项?(当原始列表有重复项时),python,algorithm,language-design,permutation,Python,Algorithm,Language Design,Permutation,人们普遍认为n个不同符号的列表有n个!排列。然而,当符号不明显时,在数学和其他领域,最常见的惯例似乎是只计算不同的排列。因此,列表[1,1,2]的排列通常被认为是 [1,1,2],[1,2,1],[2,1,1]。实际上,下面的C++代码精确地打印了三个: inta[]={1,1,2}; 做{ 库特这么说: 元素根据其位置而不是其值被视为唯一的 我的问题:为什么要做出这个设计决定 似乎遵循通常的约定会得到更有用的结果(事实上,这正是我想要的)……或者我缺少Python行为的一些应用程序 (例如,在
[1,1,2]
的排列通常被认为是[1,1,2],[1,2,1],[2,1,1]
。实际上,下面的C++代码精确地打印了三个:
inta[]={1,1,2};
做{
库特这么说:
元素根据其位置而不是其值被视为唯一的
我的问题:为什么要做出这个设计决定
似乎遵循通常的约定会得到更有用的结果(事实上,这正是我想要的)……或者我缺少Python行为的一些应用程序
(例如,在代码< NExtx置换> <代码>中的算法——例如在StAdvExcel上解释的-在Python中似乎是有效的和可实现的,但是Python做一些更有效的事情,因为它不能保证基于值的字典顺序吗?如果是这样,效率的提高会被考虑吗?ed值得吗?]也许我错了,但原因似乎就在这里
您已经指定了(1,1,2),并且从您的角度来看,0索引处的1和1索引处的1是相同的-但是这不是如此,因为置换python实现使用索引而不是值
因此,如果我们看一下默认的python置换实现,就会发现它使用索引:
def permutations(iterable, r=None):
pool = tuple(iterable)
n = len(pool)
r = n if r is None else r
for indices in product(range(n), repeat=r):
if len(set(indices)) == r:
yield tuple(pool[i] for i in indices)
例如,如果您将输入更改为[1,2,3],您将得到正确的排列([(1,2,3),(1,3,2),(2,1,3),(2,3,1),(3,1,2),(3,2,1)])由于值是唯一的。通过包装itertools.permutations
,很容易获得您喜欢的行为,这可能会影响决策。如文档中所述,itertools
被设计为构建自己的迭代器时使用的构建块/工具的集合
def unique(iterable):
seen = set()
for x in iterable:
if x in seen:
continue
seen.add(x)
yield x
for a in unique(permutations([1, 1, 2])):
print a
(1, 1, 2)
(1, 2, 1)
(2, 1, 1)
但是,正如评论中指出的,这可能没有您希望的那么有效:
>>> %timeit iterate(permutations([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2]))
1 loops, best of 3: 4.27 s per loop
>>> %timeit iterate(unique(permutations([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2])))
1 loops, best of 3: 13.2 s per loop
也许如果有足够的兴趣,可以在itertools.permutations
中添加一个新函数或可选参数,以更有效地生成没有重复的置换。我不能代表itertools.permutations
的设计者说话(雷蒙德·赫廷格),但在我看来,这项设计有两点可取之处:
首先,如果您使用next\u permutation
风格的方法,那么您将被限制为传入支持线性排序的对象。而itertools.permutations
提供任何类型的对象的置换。想象一下这将是多么烦人:
>>> list(itertools.permutations([1+2j, 1-2j, 2+j, 2-j]))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: no ordering relation is defined for complex numbers
>列表(itertools.置换([1+2j,1-2j,2+j,2-j]))
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
TypeError:没有为复数定义排序关系
第二,通过不测试对象上的相等性,itertools.permutations
避免了在通常情况下调用\uuuuuuueq\uuuu
方法的成本,而这种方法在通常情况下是不必要的
基本上,itertools.permutations
以可靠且廉价的方式解决了常见问题。当然有一个论点认为itertools
应该提供一个避免重复置换的函数,但这样一个函数应该是对itertools.permutations
的补充,而不是取而代之。为什么不编写这样一个函数呢函数并提交补丁?我接受Gareth Rees的回答,认为这是最吸引人的解释(Python库设计者的回答不多)也就是说,Python的itertools.permutations
不会比较元素的值。想想看,这就是问题所要问的,但我现在明白了它是如何被视为一种优势的,这取决于人们通常使用itertools.permutations
为了完整起见,我比较了三种生成所有不同排列的方法。方法1在内存和时间方面效率都很低,但需要的新代码最少,它是包装Python的itertools.permutations
,如zeekay的答案所示。方法2是基于生成器的C++的下一个排列
,来自.Method 3是我写的更接近的方法;它修改了列表(我没有把它做得太笼统)
def next_排列(l):
n=len(l)
#第一步:找到尾巴
last=n-1#尾巴从“last”到结尾
当上次>0时:
如果l[last-1]0:
小=l[最后一个-1]
大=n-1
而我[大]0
下面是一些结果。我现在更加尊重Python的内置函数:当元素全部(或几乎全部)不同时,它的速度大约是其他方法的三到四倍。当然,当有许多重复元素时,使用它是一个糟糕的主意
Some results ("us" means microseconds):
l m_itertoolsp m_nextperm_b m_nextperm_s
[1, 1, 2] 5.98 us 12.3 us 7.54 us
[1, 2, 3, 4, 5, 6] 0.63 ms 2.69 ms 1.77 ms
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 6.93 s 13.68 s 8.75 s
[1, 2, 3, 4, 6, 6, 6] 3.12 ms 3.34 ms 2.19 ms
[1, 2, 2, 2, 2, 3, 3, 3, 3, 3] 2400 ms 5.87 ms 3.63 ms
[1, 1, 1, 1, 1, 1, 1, 1, 1, 2] 2320000 us 89.9 us 51.5 us
[1, 1, 2, 2, 3, 3, 4, 4, 4, 4, 4, 4] 429000 ms 361 ms 228 ms
代码是任何人都想探索的。我发现同样令人惊讶的是,itertools
没有更直观的唯一排列概念。对于任何严肃的应用程序,生成重复排列只是为了选择其中的唯一排列是不可能的
我已经编写了自己的迭代生成器函数,其行为类似于itertools。排列
,但不返回重复项。仅考虑原始列表的排列,可以使用标准itertools
库创建子列表
def unique_permutations(t):
lt = list(t)
lnt = len(lt)
if lnt == 1:
yield lt
st = set(t)
for d in st:
lt.remove(d)
for perm in unique_permutations(lt):
yield [d]+perm
lt.append(d)
回顾这个老问题,现在最简单的方法就是使用。根据Python,它确实保证了字典顺序。上面的输出示例没有看到
def unique_permutations(t):
lt = list(t)
lnt = len(lt)
if lnt == 1:
yield lt
st = set(t)
for d in st:
lt.remove(d)
for perm in unique_permutations(lt):
yield [d]+perm
lt.append(d)