私にはグループがあり、各人がグループ内の他のすべての人と1対1でミーティングを行うことを望んでいます。ある人は一度に一人しか会えないので、次のことをしたいと思います。
必要な入力/出力の観点から問題を示すために、次のリストがあるとします。
>>> people = ['Dave', 'Mary', 'Susan', 'John']
次の出力を生成したいと思います。
>>> for round in make_rounds(people):
>>> print(round)
[('Dave', 'Mary'), ('Susan', 'John')]
[('Dave', 'Susan'), ('Mary', 'John')]
[('Dave', 'John'), ('Mary', 'Susan')]
奇数の人がいた場合、次の結果が期待できます。
>>> people = ['Dave', 'Mary', 'Susan']
>>> for round in make_rounds(people):
>>> print(round)
[('Dave', 'Mary')]
[('Dave', 'Susan')]
[('Mary', 'Susan')]
この問題の鍵は、(理由の範囲内で)パフォーマンスを発揮するソリューションが必要なことです。 が機能するコードを作成しましたが、people
のサイズが大きくなると、指数関数的に遅くなります。コードが非効率的であるかどうか、または単に問題のパラメーターに拘束されているかどうかを知るために、パフォーマンスの高いアルゴリズムを作成することについて十分に知りません。
ステップ1は簡単です:itertools.combinations
を使用してすべての可能なペアリングを取得できます。
>>> from itertools import combinations
>>> people_pairs = set(combinations(people, 2))
>>> print(people_pairs)
{('Dave', 'Mary'), ('Dave', 'Susan'), ('Dave', 'John'), ('Mary', 'Susan'), ('Mary', 'John'), ('Susan', 'John')}
ラウンド自体を解決するために、私は次のようなラウンドを構築しています。
round
リストを作成しますcombinations
メソッドを使用して計算されたpeople_pairs
セットのコピーを反復処理しますround
内にその個人がすでに含まれている既存のペアがあるかどうかを確認しますpeople_pairs
リストからペアを削除します。rounds
リストに追加しますpeople_pairs
には、最初のラウンドに参加しなかったペアのみが含まれるようになったため、もう一度やり直してください最終的に、これは望ましい結果を生み出し、残りがなくなり、すべてのラウンドが計算されるまで、私の人々のペアを削り落とします。これにはばかげた回数の反復が必要であることがすでにわかりますが、これを行うためのより良い方法はわかりません。
これが私のコードです:
from itertools import combinations
# test if person already exists in any pairing inside a round of pairs
def person_in_round(person, round):
is_in_round = any(person in pair for pair in round)
return is_in_round
def make_rounds(people):
people_pairs = set(combinations(people, 2))
# we will remove pairings from people_pairs whilst we build rounds, so loop as long as people_pairs is not empty
while people_pairs:
round = []
# make a copy of the current state of people_pairs to iterate over safely
for pair in set(people_pairs):
if not person_in_round(pair[0], round) and not person_in_round(pair[1], round):
round.append(pair)
people_pairs.remove(pair)
yield round
https://mycurvefit.com を使用してリストサイズが100〜300の場合にこのメソッドのパフォーマンスをプロットすると、1000人のリストのラウンドの計算にはおそらく約100分かかることがわかります。これを行うためのより効率的な方法はありますか?
注:私は実際には1000人の会議を開催しようとはしていません:)これはマッチング/組み合わせ論の問題を表す単純な例です ' m解決しようとしています。
これは、ウィキペディアの記事 ラウンドロビントーナメント で説明されているアルゴリズムの実装です。
from itertools import cycle , islice, chain
def round_robin(iterable):
items = list(iterable)
if len(items) % 2 != 0:
items.append(None)
fixed = items[:1]
cyclers = cycle(items[1:])
rounds = len(items) - 1
npairs = len(items) // 2
return [
list(Zip(
chain(fixed, islice(cyclers, npairs-1)),
reversed(list(islice(cyclers, npairs)))
))
for _ in range(rounds)
for _ in [next(cyclers)]
]
インデックスだけを生成しますが(1000の名前=を思い付くのに問題があるため)、1000の数値の場合、実行時間は約4秒です。
他のすべてのアプローチの主な問題-それらはペアを使用してそれらと連携し、ペアがたくさんあり、実行時間がはるかに長くなっています。私のアプローチは、ペアではなく、人との作業で異なります。私はその人を彼が会わなければならない他の人のリストにマップするdict()
を持っています、そしてこれらのリストは最大でNアイテムの長さです(ペアのようにN ^ 2ではありません)。したがって、時間の節約になります。
#!/usr/bin/env python
from itertools import combinations
from collections import defaultdict
pairs = combinations( range(6), 2 )
pdict = defaultdict(list)
for p in pairs :
pdict[p[0]].append( p[1] )
while len(pdict) :
busy = set()
print '-----'
for p0 in pdict :
if p0 in busy : continue
for p1 in pdict[p0] :
if p1 in busy : continue
pdict[p0].remove( p1 )
busy.add(p0)
busy.add(p1)
print (p0, p1)
break
# remove empty entries
pdict = { k : v for k,v in pdict.items() if len(v) > 0 }
'''
output:
-----
(0, 1)
(2, 3)
(4, 5)
-----
(0, 2)
(1, 3)
-----
(0, 3)
(1, 2)
-----
(0, 4)
(1, 5)
-----
(0, 5)
(1, 4)
-----
(2, 4)
(3, 5)
-----
(2, 5)
(3, 4)
'''
すぐにできる2つのこと:
リストから毎回セットのコピーを作成しないでください。それは時間/メモリの大きな無駄です。代わりに、各反復後にセットを1回変更します。
各ラウンドで別々の人々のセットを維持します。セット内の人物を検索すると、ラウンド全体をループするよりも1桁速くなります。
例:
def make_rounds(people):
people_pairs = set(combinations(people, 2))
while people_pairs:
round = set()
people_covered = set()
for pair in people_pairs:
if pair[0] not in people_covered \
and pair[1] not in people_covered:
round.add(pair)
people_covered.update(pair)
people_pairs -= round # remove thi
yield round
高速ルックアップが必要な場合は、ハッシュ/ディクテーションが最適です。 dict
ではなくlist
で各ラウンドに誰が参加したかを追跡すると、はるかに高速になります。
アルゴリズムを取り入れているので、大きなO表記を研究することは、どのデータ構造がどのような操作に適しているかを知るのに役立ちます。このガイドを参照してください: https://wiki.python.org/moin/TimeComplexity 時間計算量Pythonビルトイン)については、リスト内の項目はO(n)であり、入力のサイズに比例してスケーリングすることを意味します。したがって、ループ内にあるため、最終的にO(n ^ 2)以下になります。 dictの場合、ルックアップは通常O(1)です。つまり、入力のサイズは関係ありません。
また、組み込みをオーバーライドしないでください。 round
をround_
に変更しました
from itertools import combinations
# test if person already exists in any pairing inside a round of pairs
def person_in_round(person, people_dict):
return people_dict.get(person, False)
def make_rounds(people):
people_pairs = set(combinations(people, 2))
people_in_round = {}
# we will remove pairings from people_pairs whilst we build rounds, so loop as long as people_pairs is not empty
while people_pairs:
round_ = []
people_dict = {}
# make a copy of the current state of people_pairs to iterate over safely
for pair in set(people_pairs):
if not person_in_round(pair[0], people_dict) and not person_in_round(pair[1], people_dict):
round_.append(pair)
people_dict[pair[0]] = True
people_dict[pair[1]] = True
people_pairs.remove(pair)
yield round_
何かが足りないかもしれませんが(まったく珍しいことではありません)、これは、各チームが他のすべてのチームを1回だけプレイする昔ながらのラウンドロビントーナメントのように聞こえます。
これを「手作業で」処理するO(n ^ 2)メソッドがあり、「機械で」問題なく機能します。 1つの良い説明が見つかります ラウンドロビントーナメントに関するウィキペディアの記事に 。
そのO(n ^ 2)について:n-1またはnラウンドのいずれかがあり、それぞれが1つを除くすべてのテーブルエントリをローテーションするためのO(n) 1ステップとO(n)各ラウンドでn//2
一致を列挙する手順。二重リンクリストを使用してローテーションO(1)を作成できますが、一致はまだO(n)です。したがって、O(n)* O(n)= O(n ^ 2)です。
これは私のコンピューターで約45秒かかります
def make_rnds(people):
people_pairs = set(combinations(people, 2))
# we will remove pairings from people_pairs whilst we build rnds, so loop as long as people_pairs is not empty
while people_pairs:
rnd = []
rnd_set = set()
peeps = set(people)
# make a copy of the current state of people_pairs to iterate over safely
for pair in set(people_pairs):
if pair[0] not in rnd_set and pair[1] not in rnd_set:
rnd_set.update(pair)
rnd.append(pair)
peeps.remove(pair[0])
peeps.remove(pair[1])
people_pairs.remove(pair)
if not peeps:
break
yield rnd
関数を削除しましたperson_in_rnd
関数呼び出しで失われる時間を削減し、rnd_setおよびpeepsという変数を追加しました。 rnd_setは、これまでのラウンドの全員のセットであり、ペアとの一致をチェックするために使用されます。 peepsはコピーされた人々のセットであり、rndにペアを追加するたびに、それらの個人をpeepsから削除します。これにより、ピープが空になると、つまり全員がラウンドに入ると、すべての組み合わせの反復を停止できます。