web-dev-qa-db-ja.com

Pythonのitertools.permutationsに重複が含まれているのはなぜですか? (元のリストに重複がある場合)

Ndistinct記号のリストにはn!順列。ただし、記号が明確でない場合、数学およびその他の場所で最も一般的な規則は、明確な順列のみをカウントすることです。したがって、リスト[1, 1, 2]の順列は通常、
[1, 1, 2], [1, 2, 1], [2, 1, 1]。実際、次のC++コードはこれら3つを正確に出力します。

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ドキュメント は次のように述べています。

要素は、値ではなく位置に基づいて一意として扱われます。

私の質問:なぜこの設計決定が行われたのですか?

通常の慣習に従うと、より有用な結果が得られるようです(実際、それは通常、まさに私が望んでいるものです)...または、Pythonの動作のいくつかのアプリケーションに欠けているものはありますか?

[それとも実装上の問題ですか? next_permutationのようなアルゴリズム—たとえば、StackOverflow here(by(by me)) および ここにO(1) — Pythonで効率的かつ実装可能と思われますが、Pythonは、値に基づく辞書式順序を保証しないため、さらに効率的ですか?そうであれば、効率の向上が考慮されました価値がある?]

49
ShreevatsaR

私はitertools.permutations(レイモンドヘッティンガー)のデザイナーについて話すことはできませんが、デザインに賛成する点がいくつかあるようです。

まず、next_permutationスタイルのアプローチを使用した場合、線形順序付けをサポートするオブジェクトの受け渡しに制限されます。一方、itertools.permutationsanyオブジェクトの種類の順列を提供します。これがいらいらすることを想像してみてください。

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

第2に、オブジェクトの等価性をテストしないことにより、itertools.permutationsは、必要のない通常の場合に__eq__メソッドを呼び出すコストを支払うことを回避します。

基本的に、itertools.permutationsは、一般的なケースを確実かつ安価に解決します。確かにitertoolsが重複順列を回避する関数を提供するべきであるという議論がありますが、そのような関数はitertools.permutationsに追加するのではなく、追加する必要があります。そのような関数を書いて、パッチを提出してみませんか?

27
Gareth Rees

私はGareth Reesの回答を最も魅力的な説明として受け入れています(Pythonライブラリデザイナーからの回答を除く))。つまり、Pythonのitertools.permutationsは値を比較しません考えてみると、これが質問の質問ですが、itertools.permutationsの一般的な用途に応じて、それが利点としてどのように見なされるかがわかります。

完全を期すために、すべてのdistinct順列を生成する3つの方法を比較しました。方法1は、メモリおよび時間の点で非常に非効率的ですが、新しいコードは最小限で済みますが、zeekayの回答のように、Pythonのitertools.permutationsをラップすることです。方法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の組み込み関数をさらに尊重します。要素がすべて(またはほぼすべて)異なる場合、他のメソッドの約3〜4倍の速さです。もちろん、繰り返される要素が多い場合、それを使うのはひどい考えです。

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

コードは here です。

16
ShreevatsaR

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に新しい関数またはitertools.permutationsへのオプションの引数を追加して、重複なしで順列をより効率的に生成できます。

13
zeekay

itertoolsには、より直感的なユニークな順列の概念に対応する機能がないことにも驚かされます。それらの中からユニークなものを選択するためだけに反復的な順列を生成することは、深刻なアプリケーションでは問題外です。

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)
3
Sasho

多分私は間違っていますが、その理由は )にあるようです要素は、値ではなく位置に基づいて一意として扱われます。入力要素は一意であり、各順列には繰り返し値はありません。 ' (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)])値は一意であるため。

1