ping98k commited on
Commit
f92590a
·
1 Parent(s): 5831b86

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +54 -21
main.py CHANGED
@@ -4,31 +4,46 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
4
  from tqdm import tqdm
5
  import time
6
 
 
7
  NUM_TOP_PICKS = int(os.getenv("NUM_TOP_PICKS", 5))
 
8
  POOL_SIZE = int(os.getenv("POOL_SIZE", 20))
 
9
  MAX_WORKERS = int(os.getenv("MAX_WORKERS", 10))
10
 
 
 
 
11
  def score(player):
12
  time.sleep(5)
13
  return random.randint(1, 10)
14
 
15
- def play(a, b):
16
- time.sleep(5)
17
- return a if score(a) >= score(b) else b
 
 
18
 
 
 
19
  def precompute_scores(players, executor):
20
  """Compute all scores in parallel once and return a dict."""
 
21
  futures = {executor.submit(score, p): p for p in players}
22
  scores = {}
 
23
  for fut in tqdm(as_completed(futures), total=len(futures), desc="Scoring"):
24
  p = futures[fut]
25
  scores[p] = fut.result()
26
  return scores
27
 
28
- def tournament_round(pairs, executor):
 
 
29
  """Play a batch of matches in parallel; returns list of (winner, loser)."""
30
- futures = {executor.submit(play, a, b): (a, b) for a, b in pairs}
31
  results = []
 
32
  for fut in tqdm(as_completed(futures), total=len(futures),
33
  desc="Tournament round", leave=False):
34
  a, b = futures[fut]
@@ -37,54 +52,72 @@ def tournament_round(pairs, executor):
37
  results.append((w, loser))
38
  return results
39
 
40
- def tournament(players, executor):
 
 
41
  lost_to = {}
42
  current = players[:]
 
43
  while len(current) > 1:
 
44
  pairs = [(current[i], current[i+1]) for i in range(0, len(current)-1, 2)]
45
- round_results = tournament_round(pairs, executor)
46
  next_round = [w for w, _ in round_results]
 
47
  for w, loser in round_results:
48
  lost_to[loser] = w
 
49
  if len(current) % 2 == 1:
50
  next_round.append(current[-1])
51
  current = next_round
 
52
  return current[0], lost_to
53
 
 
54
  def get_candidates(champion, lost_to):
 
55
  return [p for p, o in lost_to.items() if o == champion] + [champion]
56
 
57
- def playoff(candidates, executor):
 
 
58
  wins = {p: 0 for p in candidates}
 
59
  pairs = [(candidates[i], candidates[j])
60
  for i in range(len(candidates)) for j in range(i+1, len(candidates))]
61
- futures = {executor.submit(play, a, b): (a, b) for a, b in pairs}
 
62
  for fut in tqdm(as_completed(futures), total=len(futures),
63
  desc="Playoff", leave=False):
64
  winner = fut.result()
65
  wins[winner] += 1
 
66
  return sorted(candidates, key=lambda p: wins[p], reverse=True)
67
 
68
- def get_top(players, executor, k=NUM_TOP_PICKS):
69
- champion, lost_to = tournament(players, executor)
70
- runnerup = lost_to.get(champion)
71
- finalists = [champion] + ([runnerup] if runnerup else [])
72
- semifinalists = [p for p, o in lost_to.items()
73
- if o in finalists and p not in finalists]
 
 
 
 
74
  candidates = set(finalists + semifinalists + get_candidates(champion, lost_to))
75
- ranking = playoff(list(candidates), executor)
76
  return ranking[:k]
77
 
78
  if __name__ == "__main__":
 
79
  all_players = [f"S{i}" for i in range(1, 101)]
80
- # reuse one executor for everything
81
  with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
82
- # 1) Precompute scores
83
  scores = precompute_scores(all_players, executor)
84
 
85
- # 2) Pick top N by score
86
  top_n_players = sorted(all_players, key=lambda p: scores[p], reverse=True)[:POOL_SIZE]
87
 
88
- # 3) Run tournament+playoff
89
- top5 = get_top(top_n_players, executor)
90
  print("🏆 Top picks:", top5)
 
4
  from tqdm import tqdm
5
  import time
6
 
7
+ # Number of top picks to return (default 5)
8
  NUM_TOP_PICKS = int(os.getenv("NUM_TOP_PICKS", 5))
9
+ # Initial pool size after scoring (default 20)
10
  POOL_SIZE = int(os.getenv("POOL_SIZE", 20))
11
+ # Maximum number of worker threads for parallel execution
12
  MAX_WORKERS = int(os.getenv("MAX_WORKERS", 10))
13
 
14
+ # -----------------------------------------------------------------------------
15
+ # score: Simulate an expensive scoring operation for a player.
16
+ # Sleeps for 5 seconds then returns a random score between 1 and 10.
17
  def score(player):
18
  time.sleep(5)
19
  return random.randint(1, 10)
20
 
21
+ # play: Determine match winner using precomputed scores.
22
+ # Compare two players' scores in O(1) time without additional delays.
23
+ def play(a, b, scores):
24
+ # Return 'a' if its score >= b's score, else 'b'
25
+ return a if scores[a] >= scores[b] else b
26
 
27
+ # precompute_scores: Batch parallel scoring of all players.
28
+ # Returns a dict mapping player -> their computed score.
29
  def precompute_scores(players, executor):
30
  """Compute all scores in parallel once and return a dict."""
31
+ # Submit all score() calls to the executor
32
  futures = {executor.submit(score, p): p for p in players}
33
  scores = {}
34
+ # Collect results as they complete
35
  for fut in tqdm(as_completed(futures), total=len(futures), desc="Scoring"):
36
  p = futures[fut]
37
  scores[p] = fut.result()
38
  return scores
39
 
40
+ # tournament_round: Play one elimination round in parallel.
41
+ # Takes pairs of players, returns a list of (winner, loser) tuples.
42
+ def tournament_round(pairs, executor, scores):
43
  """Play a batch of matches in parallel; returns list of (winner, loser)."""
44
+ futures = {executor.submit(play, a, b, scores): (a, b) for a, b in pairs}
45
  results = []
46
+ # As matches complete, record winners and losers
47
  for fut in tqdm(as_completed(futures), total=len(futures),
48
  desc="Tournament round", leave=False):
49
  a, b = futures[fut]
 
52
  results.append((w, loser))
53
  return results
54
 
55
+ # tournament: Run full single-elimination bracket on a player list.
56
+ # Returns the champion and a map of who each loser lost to.
57
+ def tournament(players, executor, scores):
58
  lost_to = {}
59
  current = players[:]
60
+ # Continue until only one player remains
61
  while len(current) > 1:
62
+ # Pair off adjacent players
63
  pairs = [(current[i], current[i+1]) for i in range(0, len(current)-1, 2)]
64
+ round_results = tournament_round(pairs, executor, scores)
65
  next_round = [w for w, _ in round_results]
66
+ # Record loss relationships
67
  for w, loser in round_results:
68
  lost_to[loser] = w
69
+ # Handle odd player out (bye)
70
  if len(current) % 2 == 1:
71
  next_round.append(current[-1])
72
  current = next_round
73
+ # Final remaining player is champion
74
  return current[0], lost_to
75
 
76
+ # get_candidates: Identify players who lost directly to the champion.
77
  def get_candidates(champion, lost_to):
78
+ # Include all who lost to champion + champion itself
79
  return [p for p, o in lost_to.items() if o == champion] + [champion]
80
 
81
+ # playoff: Conduct a round-robin among candidates to refine ranking.
82
+ # Returns candidates sorted by number of wins descending.
83
+ def playoff(candidates, executor, scores):
84
  wins = {p: 0 for p in candidates}
85
+ # Generate all unique matchups
86
  pairs = [(candidates[i], candidates[j])
87
  for i in range(len(candidates)) for j in range(i+1, len(candidates))]
88
+ futures = {executor.submit(play, a, b, scores): (a, b) for a, b in pairs}
89
+ # Tally wins as matches complete
90
  for fut in tqdm(as_completed(futures), total=len(futures),
91
  desc="Playoff", leave=False):
92
  winner = fut.result()
93
  wins[winner] += 1
94
+ # Sort by wins (highest first)
95
  return sorted(candidates, key=lambda p: wins[p], reverse=True)
96
 
97
+ # get_top: Main orchestration to get top K players.
98
+ # 1) Run tournament to identify top bracket players
99
+ # 2) Gather candidates (champion, runner-up, semifinalists)
100
+ # 3) Conduct playoff to finalize top K ordering
101
+ # Returns a list of top 'k' players.
102
+ def get_top(players, executor, scores, k=NUM_TOP_PICKS):
103
+ champion, lost_to = tournament(players, executor, scores)
104
+ runnerup = lost_to.get(champion)
105
+ finalists = [champion] + ([runnerup] if runnerup else [])
106
+ semifinalists = [p for p, o in lost_to.items() if o in finalists and p not in finalists]
107
  candidates = set(finalists + semifinalists + get_candidates(champion, lost_to))
108
+ ranking = playoff(list(candidates), executor, scores)
109
  return ranking[:k]
110
 
111
  if __name__ == "__main__":
112
+ # Create list of 100 players labeled S1..S100
113
  all_players = [f"S{i}" for i in range(1, 101)]
 
114
  with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
115
+ # 1) Compute scores once
116
  scores = precompute_scores(all_players, executor)
117
 
118
+ # 2) Select top N players by score
119
  top_n_players = sorted(all_players, key=lambda p: scores[p], reverse=True)[:POOL_SIZE]
120
 
121
+ # 3) Run optimized tournament + playoff using cached scores
122
+ top5 = get_top(top_n_players, executor, scores)
123
  print("🏆 Top picks:", top5)