[英]Generating tournament brackets / Finding subgraphs
我正在嘗試制作一個類似於瑞士風格的括號生成器,但與瑞士不同的是,它需要最近一輪的結果來生成下一輪游戲,而是分 2 輪繪制游戲。
我決定嘗試使用某種評分系統(Glicko、elo 等)進行播種 - 每個玩家都以默認評分開始(因此初始匹配是隨機的),然后繪制匹配以使平方和評分差異很小,並且在每批匹配后,評分都會根據結果進行更新。 但是,我還必須防止 2 名玩家玩不止一次(尤其是在同一輪中)。
我考慮將每一輪表示為一個圖 - 所有“可能的”匹配都是邊,每個頂點都是玩家,所以可能的匹配將是 n 個頂點的完整圖(然后我可以設置之前的重復匹配回合有一個非常高的“距離”,所以他們永遠不會被選中)。 然后問題變成了找到正確的子圖,使得每個頂點恰好連接兩次,並且距離的平方和最小化。
這聽起來有點類似於旅行商問題,但匹配可以完全“不相交” - 如果我們有:
1 vs 2
2 vs 3
3 vs 1
4 vs 5
5 vs 6
6 vs 4
當我們將其表示為圖表時,兩組 3 是斷開的。
我還沒有為此想出一個有效的算法,也沒有找到一個已經解決的類似問題。
我使用networkx.min_weight_matching
實現了類似瑞士的錦標賽配對算法。
配對的邏輯在方法tournament.dist
中,它定義了將兩個獲勝次數不同的玩家配對在一起的“懲罰”; 和tournament.get_next_round
。
SOS(對手獲勝次數的總和)和 SOSOS(對手的 sos 總和)被用作決勝局。
我沒有實現一個方法來移除玩家一輪或添加一個再見玩家以防玩家數量為奇數。
from networkx import Graph, min_weight_matching
class Player:
def __init__(self, name, nwins=0):
self.name = name
self.nwins = 0
self.sos = 0
self.sosos = 0
class DoubleSwissTournament:
def __init__(self, list_of_player_names=()):
self.n_players = len(list_of_player_names)
self.player_list = [Player(name) for name in list_of_player_names]
self.prev_matches = {i: set() for i in range(self.n_players)}
self.rounds = []
self.coeffs = {'nwins':100, 'sos':10, 'sosos':1}
def add_player(self, name, nwins=0):
self.player_list.append(Player(name,nwins=nwins))
self.prev_matches[self.n_players] = set()
self.n_players += 1
def recompute_sos(self):
for p,opponents in self.prev_matches.items():
self.player_list[p].sos = sum(self.player_list[q].nwins for q in opponents)
def recompute_sosos(self):
for p,opponents in self.prev_matches.items():
self.player_list[p].sosos = sum(self.player_list[q].sos for q in opponents)
def dist(self, p, q):
x,y = self.player_list[p], self.player_list[q]
return sum(coeff * (getattr(x,score) - getattr(y,score))**2
for score,coeff in self.coeffs.items())
def gen_next_round(self):
g = Graph()
g.add_nodes_from(range(self.n_players))
g.add_weighted_edges_from(
(p, q, self.dist(p,q))
for p,opponents in self.prev_matches.items()
for q in set(range(p)).difference(opponents)
)
m1 = min_weight_matching(g)
g.remove_edges_from(m1)
m2 = min_weight_matching(g)
round = m1.union(m2)
self.rounds.append(round)
return round
def add_results(self, results):
for p,q, winner in results:
self.prev_matches[p].add(q)
self.prev_matches[q].add(p)
self.player_list[winner].nwins += 1
self.recompute_sos()
self.recompute_sosos()
def get_standings(self):
players = sorted(self.player_list, key=lambda p:(p.nwins,p.sos,p.sosos), reverse=True)
ranks = list(range(1,len(players)+1))
for i in range(1,len(players)):
if ((players[i].nwins,players[i].sos,players[i].sosos)
== (players[i-1].nwins,players[i-1].sos,players[i-1].sosos)):
ranks[i] = ranks[i-1]
return [(r,p.name,p.nwins,p.sos,p.sosos) for r,p in zip(ranks,players)]
def str_standings(self):
l = self.get_standings()
l = [('','name','wins','sos','sosos')] + l
maxlen = max(len(name) for _,name,_,_,_ in l)
return '\n'.join('{:2}. {:{width}} {:>4} {:>4} {:>4}'.format(*x, width=maxlen) for x in l)
from random import choice
def main():
player_names = ['Alice', 'Bob', 'Chen', 'David', 'Elena', 'Fanfan', 'Gregory', 'Han', 'Irma', 'Jan', 'Kim', 'Lili', 'Mehdi', 'Noah', 'Oskar', 'Penelope']
tournament = DoubleSwissTournament(player_names)
print('STANDINGS BEFORE ROUND 1')
print(tournament.str_standings())
for round_number in (1,2,3):
print('\nROUND {}: PAIRINGS'.format(round_number))
round = tournament.gen_next_round()
print([(tournament.player_list[p].name,tournament.player_list[q].name)
for p,q in round])
print('\nROUND {}: RESULTS'.format(round_number))
results = [(p,q,choice((p,q))) for p,q in round]
print(['{} {}-{} {}'.format(tournament.player_list[p].name,int(p==w),int(q==w),tournament.player_list[q].name)
for p,q,w in results])
tournament.add_results(results)
print('\nSTANDINGS AFTER ROUND {}'.format(round_number))
print(tournament.str_standings())
return tournament
if __name__ == '__main__':
tournament = main()
Output:
STANDINGS BEFORE ROUND 1
. name wins sos sosos
1. Alice 0 0 0
1. Bob 0 0 0
1. Chen 0 0 0
1. David 0 0 0
1. Elena 0 0 0
1. Fanfan 0 0 0
1. Gregory 0 0 0
1. Han 0 0 0
1. Irma 0 0 0
1. Jan 0 0 0
1. Kim 0 0 0
1. Lili 0 0 0
1. Mehdi 0 0 0
1. Noah 0 0 0
1. Oskar 0 0 0
1. Penelope 0 0 0
ROUND 1: PAIRINGS
[('Kim', 'Fanfan'), ('Oskar', 'Bob'), ('Irma', 'Han'), ('Jan', 'Gregory'), ('Kim', 'Elena'), ('Penelope', 'Bob'), ('Mehdi', 'David'), ('Oskar', 'Alice'), ('Noah', 'David'), ('Penelope', 'Alice'), ('Mehdi', 'Chen'), ('Lili', 'Fanfan'), ('Irma', 'Gregory'), ('Noah', 'Chen'), ('Jan', 'Han'), ('Lili', 'Elena')]
ROUND 1: RESULTS
['Kim 0-1 Fanfan', 'Oskar 1-0 Bob', 'Irma 1-0 Han', 'Jan 1-0 Gregory', 'Kim 0-1 Elena', 'Penelope 1-0 Bob', 'Mehdi 1-0 David', 'Oskar 0-1 Alice', 'Noah 1-0 David', 'Penelope 0-1 Alice', 'Mehdi 0-1 Chen', 'Lili 1-0 Fanfan', 'Irma 1-0 Gregory', 'Noah 1-0 Chen', 'Jan 0-1 Han', 'Lili 1-0 Elena']
STANDINGS AFTER ROUND 1
. name wins sos sosos
1. Alice 2 2 4
1. Lili 2 2 4
3. Irma 2 1 6
3. Noah 2 1 6
5. Chen 1 3 2
5. Han 1 3 2
7. Elena 1 2 4
7. Fanfan 1 2 4
7. Oskar 1 2 4
7. Penelope 1 2 4
11. Jan 1 1 6
11. Mehdi 1 1 6
13. David 0 3 2
13. Gregory 0 3 2
15. Bob 0 2 4
15. Kim 0 2 4
ROUND 2: PAIRINGS
[('Noah', 'Irma'), ('Lili', 'Alice'), ('Penelope', 'Elena'), ('Lili', 'Noah'), ('Gregory', 'Bob'), ('Kim', 'Bob'), ('Elena', 'Mehdi'), ('Penelope', 'Han'), ('Mehdi', 'Jan'), ('Oskar', 'Jan'), ('Han', 'Chen'), ('Kim', 'David'), ('Alice', 'Irma'), ('Gregory', 'David'), ('Oskar', 'Fanfan'), ('Fanfan', 'Chen')]
ROUND 2: RESULTS
['Noah 1-0 Irma', 'Lili 0-1 Alice', 'Penelope 1-0 Elena', 'Lili 1-0 Noah', 'Gregory 1-0 Bob', 'Kim 0-1 Bob', 'Elena 1-0 Mehdi', 'Penelope 0-1 Han', 'Mehdi 1-0 Jan', 'Oskar 0-1 Jan', 'Han 1-0 Chen', 'Kim 0-1 David', 'Alice 0-1 Irma', 'Gregory 0-1 David', 'Oskar 1-0 Fanfan', 'Fanfan 0-1 Chen']
STANDINGS AFTER ROUND 2
. name wins sos sosos
1. Irma 3 10 37
2. Alice 3 10 35
3. Noah 3 10 34
4. Han 3 9 36
5. Lili 3 9 34
6. Chen 2 9 34
7. Penelope 2 9 31
8. Jan 2 8 32
9. Mehdi 2 8 30
10. Elena 2 7 32
11. Oskar 2 7 30
12. David 2 6 32
13. Gregory 1 8 29
14. Fanfan 1 7 31
15. Bob 1 5 30
16. Kim 0 6 25
ROUND 3: PAIRINGS
[('Lili', 'Han'), ('Elena', 'David'), ('Elena', 'Jan'), ('Chen', 'Alice'), ('Noah', 'Alice'), ('Fanfan', 'Bob'), ('Oskar', 'Kim'), ('Han', 'Noah'), ('Gregory', 'Kim'), ('Fanfan', 'Gregory'), ('Penelope', 'Jan'), ('Oskar', 'Mehdi'), ('Penelope', 'Mehdi'), ('Bob', 'David'), ('Irma', 'Lili'), ('Chen', 'Irma')]
ROUND 3: RESULTS
['Lili 0-1 Han', 'Elena 0-1 David', 'Elena 0-1 Jan', 'Chen 0-1 Alice', 'Noah 0-1 Alice', 'Fanfan 0-1 Bob', 'Oskar 1-0 Kim', 'Han 0-1 Noah', 'Gregory 1-0 Kim', 'Fanfan 0-1 Gregory', 'Penelope 0-1 Jan', 'Oskar 1-0 Mehdi', 'Penelope 0-1 Mehdi', 'Bob 0-1 David', 'Irma 0-1 Lili', 'Chen 1-0 Irma']
STANDINGS AFTER ROUND 3
. name wins sos sosos
1. Alice 5 20 120
2. Noah 4 23 116
3. Han 4 20 123
4. Lili 4 19 119
5. Jan 4 18 105
6. Oskar 4 15 103
7. David 4 14 103
8. Irma 3 23 116
9. Chen 3 20 121
10. Mehdi 3 19 104
11. Gregory 3 14 101
12. Penelope 2 20 108
13. Elena 2 17 106
14. Bob 2 14 95
15. Fanfan 1 16 98
16. Kim 0 16 90
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.