リストを並べ替えるとき、
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:多くの人が優れた回答を投稿したため、ここで勝者を選ぶのは難しい選択でした。 @ VincentvanderWeele 、 @ David Eisenstat 、 @ Coady 、 @ enrico.bacis および @ srgerg 可能な限り最高の順列を完璧に生成する機能を提供。 @ tobias_k そして、デイビッドもボーナスの質問に答えました(すべての順列を生成します)。正当性の証明のためのデイビッドへの追加のポイント。
@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つの欠陥を防ぎます。
この貪欲なアルゴリズムは、次のロジックによって最適なソリューションを返します。最適なソリューションに拡張される場合、プレフィックス(部分ソリューション)safeを呼び出します。明らかに、空のプレフィックスは安全であり、安全なプレフィックスがソリューション全体である場合、そのソリューションは最適です。各欲張りなステップが安全を維持することを帰納的に示すことで十分です。
貪欲なステップが欠陥を導入する唯一の方法は、アイテムタイプが1つだけ残っている場合です。この場合、続行する方法は1つだけであり、その方法は安全です。そうでない場合は、Pを考慮中のステップの直前の(安全な)プレフィックス、P 'を直後のプレフィックス、SをPを拡張する最適なソリューションとします。SがP'を拡張する場合、これで完了です。それ以外の場合、P '= Px and S = PQ and Q = yQ'とします。xとyはアイテム、QとQ 'はシーケンスです。
最初にPがyで終わらないと仮定します。アルゴリズムの選択により、xは少なくともQでyと同じ頻度になります。 xとyのみを含むQの最大部分文字列を考えます。最初の部分文字列に少なくともyと同じ数のxがある場合、xで始まる追加の欠陥を導入することなく書き換えることができます。最初の部分文字列に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))])
擬似コード:
入力の半分以上が同じ要素で構成されている場合にのみp[i]==p[i+1]
があります。この場合、同じ要素を連続したスポットに配置する以外の選択肢はありません(ピジョンホールの原理による)。
コメントで指摘したように、このアプローチでは、要素の1つが少なくともn/2
回(または奇数n
の場合はn/2+1
;これが(n+1)/2)
偶数と奇数の両方)。そのような要素は最大で2つあり、2つある場合、アルゴリズムは正常に機能します。唯一の問題は、少なくとも半分の時間で発生する要素が1つある場合です。要素を見つけて最初に処理することで、この問題を簡単に解決できます。
pythonについてこれを適切に書くのに十分な知識がないので、githubから前バージョンのOPの実装をコピーするために自由を取りました:
# Sort the list
a = sorted(lst)
# Put the element occurring more than half of the times in front (if needed)
n = len(a)
m = (n + 1) // 2
for i in range(n - m + 1):
if a[i] == a[i + m - 1]:
a = a[i:] + a[:i]
break
result = [None] * n
# Loop over the first half of the sorted list and fill all even indices of the result list
for i, elt in enumerate(a[:m]):
result[2*i] = elt
# Loop over the second half of the sorted list and fill all odd indices of the result list
for i, elt in enumerate(a[m:]):
result[2*i+1] = elt
return result
前の項目ではない、最も一般的な左の項目を取得するアルゴリズムが既に指定されていますが正しいです。ヒープを使用して最も一般的なものを追跡する最適な実装を次に示します。
import collections, heapq
def nonadjacent(keys):
heap = [(-count, key) for key, count in collections.Counter(a).items()]
heapq.heapify(heap)
count, key = 0, None
while heap:
count, key = heapq.heapreplace(heap, (count, key)) if count else heapq.heappop(heap)
yield key
count += 1
for index in xrange(-count):
yield key
>>> a = [1,2,3,3,2,2,1]
>>> list(nonadjacent(a))
[2, 1, 2, 3, 1, 2, 3]
all再帰的なバックトラッキングアルゴリズムを使用して、「完全に並べ替えられていない」順列(隣接する位置に2つの等しい要素がない)を生成できます。実際、すべての順列を生成する唯一の違いは、最後の数値を追跡し、それに応じていくつかのソリューションを除外することです。
def unsort(lst, last=None):
if lst:
for i, e in enumerate(lst):
if e != last:
for perm in unsort(lst[:i] + lst[i+1:], e):
yield [e] + perm
else:
yield []
この形式では、関数は多くのサブリストを作成するため、あまり効率的ではありません。また、最初に最も制約の多い数値(最もカウントの大きい数値)を調べることで、速度を上げることができます。これは、数字のcounts
のみを使用した、より効率的なバージョンです。
def unsort_generator(lst, sort=False):
counts = collections.Counter(lst)
def unsort_inner(remaining, last=None):
if remaining > 0:
# most-constrained first, or sorted for pretty-printing?
items = sorted(counts.items()) if sort else counts.most_common()
for n, c in items:
if n != last and c > 0:
counts[n] -= 1 # update counts
for perm in unsort_inner(remaining - 1, n):
yield [n] + perm
counts[n] += 1 # revert counts
else:
yield []
return unsort_inner(len(lst))
これを使用して、next
完全な順列だけを生成することも、すべてを保持するlist
を生成することもできます。ただし、no完全にソートされていない順列がある場合、このジェネレーターは結果としてnoを生成することに注意してください。
>>> lst = [1,2,3,3,2,2,1]
>>> next(unsort_generator(lst))
[2, 1, 2, 3, 1, 2, 3]
>>> list(unsort_generator(lst, sort=True))
[[1, 2, 1, 2, 3, 2, 3],
... 36 more ...
[3, 2, 3, 2, 1, 2, 1]]
>>> next(unsort_generator([1,1,1]))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
この問題を回避するには、フォールバックとして他の回答で提案されているアルゴリズムの1つとこれを併用できます。これにより、完全にソートされていない置換があれば保証され、そうでない場合は適切な近似が返されます。
def unsort_safe(lst):
try:
return next(unsort_generator(lst))
except StopIteration:
return unsort_fallback(lst)
pythonでは、次のことができます。
ソートされたリストl
があると考えてください。
_length = len(l)
odd_ind = length%2
odd_half = (length - odd_ind)/2
for i in range(odd_half)[::2]:
my_list[i], my_list[odd_half+odd_ind+i] = my_list[odd_half+odd_ind+i], my_list[i]
_
これらはインプレース操作であるため、かなり高速です(O(N)
)。 _l[i] == l[i+1]
_から_l[i] == l[i+2]
_にシフトすることに注意してください。したがって、最終的な順序はランダムではありませんが、質問を理解する方法からは、探しているのはランダムではありません。
ソートされたリストを中央で分割し、2つの部分で他のすべての要素を交換するという考え方です。
_l= [1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5]
_の場合、これは_l = [3, 1, 4, 2, 5, 1, 3, 1, 4, 2, 5]
_につながります
1つの要素の存在量がリストの長さの半分以上になるとすぐに、メソッドはすべての_l[i] == l[i + 1]
_を取り除くことができません。
最も頻度の高い要素の量がリストのサイズの半分よりも小さい限り、上記は正常に機能しますが、次の関数は制限ケースも処理します(有名なオフバイワンの問題)最初の要素から始まる他のすべての要素は、最も豊富な要素でなければなりません。
_def no_adjacent(my_list):
my_list.sort()
length = len(my_list)
odd_ind = length%2
odd_half = (length - odd_ind)/2
for i in range(odd_half)[::2]:
my_list[i], my_list[odd_half+odd_ind+i] = my_list[odd_half+odd_ind+i], my_list[i]
#this is just for the limit case where the abundance of the most frequent is half of the list length
if max([my_list.count(val) for val in set(my_list)]) + 1 - odd_ind > odd_half:
max_val = my_list[0]
max_count = my_list.count(max_val)
for val in set(my_list):
if my_list.count(val) > max_count:
max_val = val
max_count = my_list.count(max_val)
while max_val in my_list:
my_list.remove(max_val)
out = [max_val]
max_count -= 1
for val in my_list:
out.append(val)
if max_count:
out.append(max_val)
max_count -= 1
if max_count:
print 'this is not working'
return my_list
#raise Exception('not possible')
return out
else:
return my_list
_
これが良いアルゴリズムです:
まず、すべての数値が発生する頻度をカウントします。答えをマップに配置します。
最も頻繁に発生する番号が最初になるように、このマップを並べ替えます。
回答の最初の番号は、ソートされたマップの最初の番号です。
最初のものが1つ小さくなったマップを再利用します。
効率を改善したい場合は、ソート手順の効率を高める方法を探してください。
ボーナスの質問に対する答えとして、これは、隣接する要素が同一にならないセットのすべての順列を見つけるアルゴリズムです。これは概念的には最も効率的なアルゴリズムであると考えています(ただし、他のアルゴリズムはより単純なコードに変換されるため、実際にはより高速になる場合があります)。ブルートフォースを使用せず、一意の順列のみを生成し、ソリューションに至らないパスは最も早い時点で切断されます。
他のすべての要素を組み合わせた場合よりも頻繁に発生するセット内の要素に対して「豊富な要素」という用語を使用し、豊富な要素の数から他の要素の数を引いた数に対して「豊富」という用語を使用します。
たとえば、セットabac
には豊富な要素がなく、セットabaca
およびaabcaa
には豊富な要素としてa
があります。および豊富度1および2それぞれ)
aaabbcd
最初:abcd
繰り返し:aab
豊富な要素:a
豊富:1
abcd、abdc、acbd、acdb、adbc、adcb、bacd、badc、bcad、
bcda、bdac、bDCA、
cabd、cadb、cbad、cbda、cdab、cdba、dabc、dacb、abac、dbca、dcab、dcba
5.1。これまでの順列での豊富な要素の最後の出現後、セットの豊富さが要素数よりも大きい場合は、次の順列にスキップします。
たとえば、これまでの順列がabc
である場合、豊富な要素a
を含むセットは、豊富度が2以下の場合にのみ挿入できます。したがって、aaaabc
は大丈夫、aaaaabc
は大丈夫です。5.2。順列の最後の出現が最初になるセットから要素を選択します。
例:これまでの順列がabcba
で、セットがab
の場合、b
を選択5.3。選択された要素を、順列の最後の出現の右側に少なくとも2箇所挿入します。
たとえば、b
を置換babca
に挿入すると、結果はbabcba
およびbabcab
5.4。結果の各順列とセットの残りでステップ5を繰り返します。
EXAMPLE:
set = abcaba
firsts = abc
repeats = aab
perm3 set select perm4 set select perm5 set select perm6
abc aab a abac ab b ababc a a ababac
ababca
abacb a a abacab
abacba
abca ab b abcba a -
abcab a a abcaba
acb aab a acab ab a acaba b b acabab
acba ab b acbab a a acbaba
bac aab b babc aa a babac a a babaca
babca a -
bacb aa a bacab a a bacaba
bacba a -
bca aab -
cab aab a caba ab b cabab a a cababa
cba aab -
このアルゴリズムは、一意の順列を生成します。順列の総数(aを切り替えることができるためaba
が2回カウントされる)を知りたい場合は、一意の順列の数に係数を掛けます。
F = N1! * N2! * ... * Nn!
nは、セット内の各要素の出現回数です。セットabcdabcaba
の場合、これは4になります! * 3! * 2! * 1!または288。これは、一意のアルゴリズムだけではなく、すべての順列を生成するアルゴリズムの非効率性を示しています。この場合のすべての順列をリストするには、一意の順列を288回だけリストしてください:-)
以下は、Javascriptでの(やや不器用な)実装です。 Pythonのような言語がこの種のものにより適している可能性があります。「abracadabra」の分離された順列を計算するためにコードスニペットを実行します。
// FIND ALL PERMUTATONS OF A SET WHERE NO ADJACENT ELEMENTS ARE IDENTICAL
function seperatedPermutations(set) {
var unique = 0, factor = 1, firsts = [], repeats = [], abund;
seperateRepeats(set);
abund = abundance(repeats);
permutateFirsts([], firsts);
alert("Permutations of [" + set + "]\ntotal: " + (unique * factor) + ", unique: " + unique);
// SEPERATE REPEATED CHARACTERS AND CALCULATE TOTAL/UNIQUE RATIO
function seperateRepeats(set) {
for (var i = 0; i < set.length; i++) {
var first, elem = set[i];
if (firsts.indexOf(elem) == -1) firsts.Push(elem)
else if ((first = repeats.indexOf(elem)) == -1) {
repeats.Push(elem);
factor *= 2;
} else {
repeats.splice(first, 0, elem);
factor *= repeats.lastIndexOf(elem) - first + 2;
}
}
}
// FIND ALL PERMUTATIONS OF THE FIRSTS USING RECURSION
function permutateFirsts(perm, set) {
if (set.length > 0) {
for (var i = 0; i < set.length; i++) {
var s = set.slice();
var e = s.splice(i, 1);
if (e[0] == abund.elem && s.length < abund.num) continue;
permutateFirsts(perm.concat(e), s, abund);
}
}
else if (repeats.length > 0) {
insertRepeats(perm, repeats);
}
else {
document.write(perm + "<BR>");
++unique;
}
}
// INSERT REPEATS INTO THE PERMUTATIONS USING RECURSION
function insertRepeats(perm, set) {
var abund = abundance(set);
if (perm.length - perm.lastIndexOf(abund.elem) > abund.num) {
var sel = selectElement(perm, set);
var s = set.slice();
var elem = s.splice(sel, 1)[0];
for (var i = perm.lastIndexOf(elem) + 2; i <= perm.length; i++) {
var p = perm.slice();
p.splice(i, 0, elem);
if (set.length == 1) {
document.write(p + "<BR>");
++unique;
} else {
insertRepeats(p, s);
}
}
}
}
// SELECT THE ELEMENT FROM THE SET WHOSE LAST OCCURANCE IN THE PERMUTATION COMES FIRST
function selectElement(perm, set) {
var sel, pos, min = perm.length;
for (var i = 0; i < set.length; i++) {
pos = perm.lastIndexOf(set[i]);
if (pos < min) {
min = pos;
sel = i;
}
}
return(sel);
}
// FIND ABUNDANT ELEMENT AND ABUNDANCE NUMBER
function abundance(set) {
if (set.length == 0) return ({elem: null, num: 0});
var elem = set[0], max = 1, num = 1;
for (var i = 1; i < set.length; i++) {
if (set[i] != set[i - 1]) num = 1
else if (++num > max) {
max = num;
elem = set[i];
}
}
return ({elem: elem, num: 2 * max - set.length});
}
}
seperatedPermutations(["a","b","r","a","c","a","d","a","b","r","a"]);
アイデアは、最も一般的なものから最も一般的なものに要素をソートし、最も一般的なものを取り、その数を減らし、降順を維持してリストに戻すことです(ただし、可能な限り繰り返しを防ぐために最後に使用した要素を最初に置くことは避けてください) 。
これは、 Counter
および bisect
を使用して実装できます。
from collections import Counter
from bisect import bisect
def unsorted(lst):
# use elements (-count, item) so bisect will put biggest counts first
items = [(-count, item) for item, count in Counter(lst).most_common()]
result = []
while items:
count, item = items.pop(0)
result.append(item)
if count != -1:
element = (count + 1, item)
index = bisect(items, element)
# prevent insertion in position 0 if there are other items
items.insert(index or (1 if items else 0), element)
return result
>>> print unsorted([1, 1, 1, 2, 3, 3, 2, 2, 1])
[1, 2, 1, 2, 1, 3, 1, 2, 3]
>>> print unsorted([1, 2, 3, 2, 3, 2, 2])
[2, 3, 2, 1, 2, 3, 2]
元の場所のリストからアイテムの最小値を(アイテムの値によって)与えるため、たとえば、ソートされた位置から1、2、3を離そうとします。
「私も」スタイルの回答はお許しください。 Coadyの回答 これに簡略化できませんでしたか?
from collections import Counter
from heapq import heapify, heappop, heapreplace
from itertools import repeat
def srgerg(data):
heap = [(-freq+1, value) for value, freq in Counter(data).items()]
heapify(heap)
freq = 0
while heap:
freq, val = heapreplace(heap, (freq+1, val)) if freq else heappop(heap)
yield val
yield from repeat(val, -freq)
Edit:python 2リストを返すバージョン:
def srgergpy2(data):
heap = [(-freq+1, value) for value, freq in Counter(data).items()]
heapify(heap)
freq = 0
result = list()
while heap:
freq, val = heapreplace(heap, (freq+1, val)) if freq else heappop(heap)
result.append(val)
result.extend(repeat(val, -freq))
return result
長さnのソート済みリストから始めます。 m = n/2とします。 0、m、1、m + 1、2、m + 2などの値を取得します。数字の半分以上が同じでない限り、連続した順序で同等の値を取得することはありません。
- 各値が現れる回数を数える
- 最も頻度の高い順に値を選択します
- 選択した値を最終出力に追加し、毎回インデックスを2ずつ増やします
- インデックスが範囲外の場合、インデックスを1にリセットします
from heapq import heapify, heappop
def distribute(values):
counts = defaultdict(int)
for value in values:
counts[value] += 1
counts = [(-count, key) for key, count in counts.iteritems()]
heapify(counts)
index = 0
length = len(values)
distributed = [None] * length
while counts:
count, value = heappop(counts)
for _ in xrange(-count):
distributed[index] = value
index = index + 2 if index + 2 < length else 1
return distributed