[解決済み] 隣接する等しい要素を持たないリストの並べ換えをすべて生成する
質問
のようなリストをソートするとき
a = [1,2,3,3,2,2,1]
sorted(a) => [1, 1, 2, 2, 2, 3, 3]
が等しい要素は、結果として得られるリストでは常に隣接しています。
等しい要素が決して(あるいは可能な限り)隣接しないようにリストをシャッフルする、という逆のタスクを達成するにはどうしたらよいでしょうか。
たとえば、上記のリストについて、可能な解決策の1つは次のとおりです。
p = [1,3,2,3,2,1,2]
より正式には、リスト
a
が与えられた場合、順列を生成し
p
の組の数を最小にするような順列を生成する。
p[i]==p[i+1]
.
リストは大きいので、すべての順列を生成してフィルタリングすることはオプションではありません。
ボーナス質問:そのようなすべての並べ換えを効率的に生成する方法は?
これは私が解答をテストするために使っているコードです。 https://gist.github.com/gebrkn/9f550094b3d24a35aebd
UPD: 多くの人が素晴らしい回答を投稿してくれたので、ここで受賞者を選ぶのは難しい選択でした。 ヴィンセントヴァンダーウィーレ , デイヴィッド アイゼンスタット , コーディ , エンリコ・バシス そして @srgerg は、最適な順列を完璧に生成する機能を提供した。 トビアス_k とDavidはボーナス問題(generate all permutations)にも答えています。正しさを証明したDavidに追加点を与えます。
VincentvanderWeeleのコードが最速のようです。
どのように解決するのですか?
これはThijserの現在不完全な擬似コードに沿ったものです。このアイデアは、残りのアイテムの種類のうち、最も頻度の高いものを、それがちょうど取られない限り取ることです。(参照 Coady の実装 を参照してください)。
import collections
import heapq
class Sentinel:
pass
def david_eisenstat(lst):
counts = collections.Counter(lst)
heap = [(-count, key) for key, count in counts.items()]
heapq.heapify(heap)
output = []
last = Sentinel()
while heap:
minuscount1, key1 = heapq.heappop(heap)
if key1 != last or not heap:
last = key1
minuscount1 += 1
else:
minuscount2, key2 = heapq.heappop(heap)
last = key2
minuscount2 += 1
if minuscount2 != 0:
heapq.heappush(heap, (minuscount2, key2))
output.append(last)
if minuscount1 != 0:
heapq.heappush(heap, (minuscount1, key1))
return output
正しさの証明
カウントk1とk2の2つのアイテムタイプについて、最適解はk1 < k2の場合k2 - k1 - 1の欠陥、k1 = k2の場合0欠陥、k1 > k2の場合k1 - k2 - 1欠陥となります。の場合は明らかである。少数要素の各インスタンスは、k1 + k2 - 1の可能性の合計のうち、最大で2つの欠陥を防止します。
この貪欲なアルゴリズムは、以下の論理により、最適な解を返します。ここでは、接頭辞(部分解)を セーフ と呼び、それが最適解に拡張される場合は 空の接頭辞は明らかに安全であり、安全な接頭辞が解全体であるなら、その解は最適である。各欲張りステップで安全性が保たれていることを帰納的に示せば十分である。
貪欲なステップが欠陥をもたらす唯一の方法は、1つのアイテムタイプだけが残っている場合であり、その場合、継続する方法は1つだけであり、その方法は安全である。そうでなければ、検討中のステップの直前の(安全な)接頭辞をP、直後の接頭辞をP'とし、Pを拡張した最適解をSとします。そうでなければ、P' = Px と S = PQ と Q = yQ' とし、x と y は項目、Q と Q' は列とする。
まずPがyで終わらないとする。アルゴリズムの選択により、Qにおいてxはyと少なくとも同じ頻度である。最初の部分文字列がyと少なくとも同じ数のxを持つなら、xから始まるように追加の欠陥を導入せずに書き直すことができる。どちらの場合も、必要に応じてP'を拡張する最適解Tを見つけます。
今、Pがyで終わっているとします。xの最初の出現を前に移動することによってQを修正します。そうすることで、最大でも1つの欠陥(xがあった場所)を導入し、1つの欠陥(yy)を取り除くことができます。
すべての解を生成する
これは tobias_k の回答です。 に加えて、現在検討中の選択肢が何らかの方法で大域的に制約されている場合にそれを検出するための効率的なテストです。生成のオーバーヘッドが出力の長さのオーダーであるため、漸近的な実行時間は最適です。最悪の場合の遅延は残念ながら2次関数であるが、より良いデータ構造で線形(最適)に短縮できるだろう。
from collections import Counter
from itertools import permutations
from operator import itemgetter
from random import randrange
def get_mode(count):
return max(count.items(), key=itemgetter(1))[0]
def enum2(prefix, x, count, total, mode):
prefix.append(x)
count_x = count[x]
if count_x == 1:
del count[x]
else:
count[x] = count_x - 1
yield from enum1(prefix, count, total - 1, mode)
count[x] = count_x
del prefix[-1]
def enum1(prefix, count, total, mode):
if total == 0:
yield tuple(prefix)
return
if count[mode] * 2 - 1 >= total and [mode] != prefix[-1:]:
yield from enum2(prefix, mode, count, total, mode)
else:
defect_okay = not prefix or count[prefix[-1]] * 2 > total
mode = get_mode(count)
for x in list(count.keys()):
if defect_okay or [x] != prefix[-1:]:
yield from enum2(prefix, x, count, total, mode)
def enum(seq):
count = Counter(seq)
if count:
yield from enum1([], count, sum(count.values()), get_mode(count))
else:
yield ()
def defects(lst):
return sum(lst[i - 1] == lst[i] for i in range(1, len(lst)))
def test(lst):
perms = set(permutations(lst))
opt = min(map(defects, perms))
slow = {perm for perm in perms if defects(perm) == opt}
fast = set(enum(lst))
print(lst, fast, slow)
assert slow == fast
for r in range(10000):
test([randrange(3) for i in range(randrange(6))])
関連
-
[解決済み] リストの要素数を取得する方法
-
[解決済み] リストのすべての並べ換えを生成するには?
-
[解決済み] リスト内のすべての要素が同一であるかどうかをチェックする
-
[解決済み】与えられた文字列のすべての並べ換えを生成する
-
[解決済み] Spyderを仮想環境で動作させるには?
-
[解決済み] 辞書のキーと値を交換するにはどうすればよいですか?
-
[解決済み] データフレームをソートした後にインデックスを更新する
-
[解決済み] Cythonのコードを含むPythonパッケージはどのように構成すればよいのでしょうか?
-
[解決済み] 単純な文字列からtimedeltaオブジェクトを作成する方法
-
[解決済み] pipの依存性/必要条件をリストアップする方法はありますか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] 2つの線分が交差しているかどうかを確認するにはどうすればよいですか?
-
[解決済み] Pythonのキャッシュライブラリはありますか?
-
[解決済み] PILからopenCVフォーマットへの変換
-
[解決済み] Pythonの要素別タプル演算(sumなど
-
[解決済み] 小数点以下1桁を取得する[重複]。
-
[解決済み] SQLAlchemy - テーブルのリストを取得する
-
[解決済み] PyMongoで.sortを使用する
-
[解決済み] matplotlib でプロットの軸、目盛、ラベルの色を変更する方法
-
[解決済み] pycharmがタブをスペースに自動変換する
-
[解決済み] 単純な文字列からtimedeltaオブジェクトを作成する方法