itertools.permutations
itertoolsのドキュメントを読んでいてpermutationsのコードの動きがよくわからなかったので調べてみた。
とりあえず、0から3までの数字の列を例に考えてみる。
[0 1 2 3]から始めて、0番目の数字(0)と1番目の数字(1)を交換、0番目の数字(1)と2番目の数字(2)を交換、0番目の数字(2)と3番目の数字(3)を交換という操作をすると次のように並び方が変化する。このとき得られる各配列は、0番目の数字が先頭にあるものの中で最小になっている。
0 1 2 3 # 初期値 0が先頭にくる配列の中で最小。 1 0 2 3 # 0と1を交換。1が先頭にくる配列の中で最小。 2 0 1 3 # 1と2を交換。2が先頭にくる配列の中で最小。 3 0 1 2 # 2と3を交換。3が先頭にくる配列の中で最小。
また、最後に得られた配列の先頭の数字を末尾に移動して、左に一つずつずらすともとの[0 1 2 3]という並び方にもどる。
同様のことは、要素が互いに異なっていて昇順にならんでいるならば成り立つので、0番目の数字を固定して、1番目より後ろに対しても同様のことができる。
0 | 1 2 3 # 初期値 0 1が先頭にくる配列のなかで最小。 0 | 2 1 3 # 1と2を交換。0 2が先頭にくる配列のなかで最小。 0 | 3 1 2 # 2と3を交換。0 3が先頭にくる配列のなかで最小。 0 1 2 3 # 1文字目より後ろを左回転。 1 | 0 2 3 # 0と1を交換。1 0が先頭にくる配列のなかで最小。 1 | 2 0 3 # 0と2を交換。1 2が先頭にくる配列のなかで最小。 1 | 3 0 2 # 2と3を交換。1 3が先頭にくる配列のなかで最小。 ...
0、1番目の数字を固定して、2番目以降の文字に対して同様にすると、この場合全ての順列が得られる。
0 1 | 2 3 # 初期値 0 1 2が先頭にくる配列のなかで最小。 0 1 | 3 2 # 2と3を交換。0 1 3が先頭にくる配列のなかで最小。 0 | 1 2 3 # 2文字目より後ろを左回転。 0 2 | 1 3 # 1と2を交換。0 2 1が先頭にくる配列のなかで最小。 0 2 | 3 1 # 1と3を交換。0 2 3が先頭にくる配列のなかで最小。 0 | 2 1 3 # 2文字目より後ろを左回転。 0 3 | 1 2 # 2と3を交換。0 3 1が先頭にくる配列のなかで最小。 0 3 | 2 1 # 1と2を交換。0 3 2が先頭にくる配列のなかで最小。 ...
ここまでをコードで書くと、次のようにforループが3つ入れ子になったものになる。
indices = [0, 1, 2, 3] for i1 in range(4): indices[0], indices[i1] = indices[i1], indices[0] for i2 in range(4-1): # 0文字目は固定 indices[1], indices[1+i2] = indices[1+i2], indices[1] for i3 in range(4-2): # 0、1文字目は固定 indices[2], indices[2+i3] = indices[2+i3], indices[2] print indices indices[2:] = indices[3:] + indices[2:3] # 2文字目より後ろを回転 indices[1:] = indices[2:] + indices[1:2] # 1文字目より後ろを回転 indices[0:] = indices[1:] + indices[0:1] # 0文字目より後ろを回転
cyclesというループを回す回数を管理するリストを使って、入れ子を次のように書き換えると入力の長さが変わっても対応できるようになる。
indices = [0, 1, 2, 3] n = len(indices) cycles = range(n, 1, -1) # cycles = [4, 3, 2] print indices while True: for i in reversed(range(len(cycles))): cycles[i] -= 1 if cycles[i] == 0: indices[i:] = indices[i+1:] + indices[i:i+1] # i番目より後ろを回転 cycles[i] = n - i # カウントをリセット else: j = n - i - cycles[i] indices[i], indices[i+j] = indices[i+j], indices[i] # i+j は -cycles[i] とも書ける。 print indices break else: break # forが最後までまわったら、whileを抜けて終了。
大体の流れはこのような感じ。forループの書き換えはいろいろな所で使えそう。