繁体   English   中英

为什么 Python 的 itertools.permutations 包含重复项? (当原始列表有重复时)

[英]Why does Python's itertools.permutations contain duplicates? (When the original list has duplicates)

普遍认为,n 个不同符号的列表有 n 个。 排列,然而,当符号不明显时,最常见的约定,在数学和其他地方。 似乎只计算不同的排列。 因此列表[1, 1, 2]的排列通常被认为是
[1, 1, 2], [1, 2, 1], [2, 1, 1] 事实上,下面的 C++ 代码正好打印出这三个:

int a[] = {1, 1, 2};
do {
    cout<<a[0]<<" "<<a[1]<<" "<<a[2]<<endl;
} while(next_permutation(a,a+3));

另一方面,Python 的itertools.permutations似乎打印了其他内容:

import itertools
for a in itertools.permutations([1, 1, 2]):
    print a

这打印

(1, 1, 2)
(1, 2, 1)
(1, 1, 2)
(1, 2, 1)
(2, 1, 1)
(2, 1, 1)

正如用户Artsiom Rudzenka 在回答中指出的那样, Python 文档是这样说的:

元素根据其 position 而非其值被视为唯一。

我的问题:为什么做出这个设计决定?

似乎遵循通常的约定会产生更有用的结果(实际上它通常正是我想要的)......或者是否有一些我缺少的 Python 行为应用程序?

[或者是一些实施问题? next_permutation中的算法(例如在此处(由我)在 StackOverflow 上解释并在此处显示为 O(1) 摊销)在 Python 中似乎有效且可实现,但 Python 做得更有效,因为它不保证字典顺序基于价值? 如果是这样,提高效率是否值得?]

我不能代表itertools.permutations的设计者(Raymond Hettinger),但在我看来,有几点支持该设计:

首先,如果您使用next_permutation样式的方法,那么您将被限制为传入支持线性排序的对象。 itertools.permutations提供任何类型的 object 的排列。 想象一下这将是多么烦人:

>>> 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.permutations避免了在通常情况下不需要支付调用__eq__方法的成本。

基本上, itertools.permutations可靠且廉价地解决了常见情况。 肯定有一个论点是itertools应该提供一个 function 以避免重复排列,但是这样的 function 应该是除了itertools.permutations之外的,而不是代替它。 为什么不写这样一个 function 并提交补丁呢?

我接受 Gareth Rees 的回答作为最吸引人的解释(缺少 Python 库设计者的回答),即 Python 的itertools.permutations不比较元素的值。 想一想,这就是问题所要问的,但我现在看到它如何被视为一种优势,这取决于人们通常使用itertools.permutations的目的。

为了完整起见,我比较了三种生成所有不同排列的方法。 方法 1 在内存和时间方面效率非常低,但需要的新代码最少,它是包装 Python 的itertools.permutations ,如 zeekay 的回答。 方法 2 是 C++ 的next_permutation的基于生成器的版本,来自这篇博文。 方法 3 是我写的更接近C++ 的next_permutation算法的东西; 它就地修改了列表(我没有把它说得太笼统)。

def next_permutationS(l):
    n = len(l)
    #Step 1: Find tail
    last = n-1 #tail is from `last` to end
    while last>0:
        if l[last-1] < l[last]: break
        last -= 1
    #Step 2: Increase the number just before tail
    if last>0:
        small = l[last-1]
        big = n-1
        while l[big] <= small: big -= 1
        l[last-1], l[big] = l[big], small
    #Step 3: Reverse tail
    i = last
    j = n-1
    while i < j:
        l[i], l[j] = l[j], l[i]
        i += 1
        j -= 1
    return last>0

以下是一些结果。 我现在更加尊重 Python 的内置 function:当元素全部(或几乎全部)不同时,它的速度大约是其他方法的三到四倍。 当然,当有很多重复元素时,使用它是一个糟糕的主意。

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

也许如果有足够的兴趣,可以将新的 function 或itertools.permutations的可选参数添加到itertools ,以更有效地生成没有重复的排列。

我还发现itertools没有 function 来获得更直观的独特排列概念,这让我感到惊讶。 仅对 select 生成重复排列,其中唯一的对于任何严肃的应用程序都是不可能的。

我编写了自己的迭代生成器 function,其行为类似于itertools.permutations但不返回重复项。 仅考虑原始列表的排列,可以使用标准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)

重新审视这个老问题,现在最简单的方法是使用more_itertools.distinct_permutations

也许我错了,但似乎原因在于'元素被视为基于其 position 的唯一性,而不是其价值。 因此,如果输入元素是唯一的,则每个排列中都不会出现重复值。 您已指定 (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)]) 因为这些值是唯一的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM