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は、値に基づく辞書式順序を保証しないため、さらに効率的ですか?そうであれば、効率の向上が考慮されました価値がある?]
私はitertools.permutations
(レイモンドヘッティンガー)のデザイナーについて話すことはできませんが、デザインに賛成する点がいくつかあるようです。
まず、next_permutation
スタイルのアプローチを使用した場合、線形順序付けをサポートするオブジェクトの受け渡しに制限されます。一方、itertools.permutations
はanyオブジェクトの種類の順列を提供します。これがいらいらすることを想像してみてください。
>>> 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
に追加するのではなく、追加する必要があります。そのような関数を書いて、パッチを提出してみませんか?
私は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 です。
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
へのオプションの引数を追加して、重複なしで順列をより効率的に生成できます。
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)
多分私は間違っていますが、その理由は )にあるようです要素は、値ではなく位置に基づいて一意として扱われます。入力要素は一意であり、各順列には繰り返し値はありません。 ' (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)])値は一意であるため。