1class Egg:
2 def __init__(self):
3 self._segments = []
4 self._filled = 0.0
5 self._broken_top = False
6 self._broken_bottom = False
7 self._tournament = None
8
9 def paint(self, *args):
10 total_new = sum(p for _, p in args)
11
12 if self._filled + total_new > 100.0:
13 raise ValueError("Too much paint")
14
15 current = self._filled
16
17 for hex_color, percentage in args:
18 pigment = self._pigment(hex_color)
19
20 start = current
21 end = current + percentage
22
23 if start < 50 < end:
24 self._segments.append((start, 50, pigment))
25 self._segments.append((50, end, pigment))
26 else:
27 self._segments.append((start, end, pigment))
28
29 current = end
30
31 self._filled += total_new
32
33 def _pigment(self, hex_color):
34 r = int(hex_color[0:2], 16)
35 g = int(hex_color[2:4], 16)
36 b = int(hex_color[4:6], 16)
37 return r + g + b
38
39 def _strength(self, top):
40 if top and self._broken_top:
41 raise TypeError("Top side is broken")
42 if not top and self._broken_bottom:
43 raise TypeError("Bottom side is broken")
44
45 low, high = (0, 50) if top else (50, 100)
46 total = 0.0
47
48 for start, end, pigment in self._segments:
49 overlap_start = max(start, low)
50 overlap_end = min(end, high)
51
52 if overlap_start < overlap_end:
53 portion = (overlap_end - overlap_start) / 50
54 total += portion * pigment
55
56 return total
57
58 def _battle(self, other, top):
59 s1 = self._strength(top)
60 s2 = other._strength(top)
61
62 if s1 > s2:
63 winner, loser = self, other
64 else:
65 winner, loser = other, self
66
67 if top:
68 loser._broken_top = True
69 else:
70 loser._broken_bottom = True
71
72 if self._tournament and self._tournament is other._tournament:
73 self._tournament.record(self, other, "top" if top else "bottom", winner)
74
75 return winner
76
77 def __mul__(self, other):
78 return self._battle(other, True)
79
80 def __matmul__(self, other):
81 return self._battle(other, False)
82
83
84class EggTournament:
85 def __init__(self):
86 self._eggs = {}
87 self._names = {}
88 self._history = {}
89 self._wins = {}
90
91 def register(self, egg, name):
92 if not name.isidentifier():
93 raise ValueError("Invalid registration name")
94
95 if name in self._eggs:
96 raise ValueError(f"Egg with name {name} has already been registered")
97
98 if egg._tournament is not None:
99 raise ValueError("An egg cannot be registered in multiple tournaments")
100
101 self._eggs[name] = egg
102 self._names[egg] = name
103 self._wins[egg] = 0
104 egg._tournament = self
105
106 def _pair_key(self, egg1, egg2):
107 return (egg1, egg2) if id(egg1) < id(egg2) else (egg2, egg1)
108
109 def record(self, egg1, egg2, side, winner):
110 if egg1 not in self._names or egg2 not in self._names:
111 return
112
113 key = self._pair_key(egg1, egg2), side
114 self._history[key] = winner
115 self._wins[winner] += 1
116
117 def __getitem__(self, item):
118 if isinstance(item, tuple):
119 egg1, egg2, side = item
120 elif isinstance(item, slice):
121 egg1, egg2, side = item.start, item.stop, item.step
122 else:
123 raise KeyError
124
125 key = self._pair_key(egg1, egg2), side
126
127 if key not in self._history:
128 raise KeyError
129
130 return self._history[key]
131
132 def __contains__(self, egg):
133 return egg in self._names
134
135 def __getattr__(self, name):
136 if name not in self._eggs:
137 raise AttributeError("Apologies, there is no such egg registered")
138
139 egg = self._eggs[name]
140 return {
141 "position": self._position(egg),
142 "victories": self._wins[egg]
143 }
144
145 @property
146 def ranking(self):
147 sorted_eggs = sorted(self._wins.items(), key=lambda x: -x[1])
148
149 ranking = {}
150 current_pos = 1
151 last_wins = None
152
153 for i, (egg, wins) in enumerate(sorted_eggs):
154 if wins != last_wins:
155 current_pos = i + 1
156
157 if current_pos not in ranking:
158 ranking[current_pos] = set()
159
160 ranking[current_pos].add(egg)
161 last_wins = wins
162
163 return ranking
164
165 def _position(self, egg):
166 for pos, eggs in self.ranking.items():
167 if egg in eggs:
168 return pos
169
170 def __rmatmul__(self, position):
171 if position not in self.ranking:
172 raise IndexError
173
174 eggs = self.ranking[position]
175
176 if len(eggs) == 1:
177 return list(eggs)[0]
178
179 return eggs
.....................F................F..E....
======================================================================
ERROR: test_ranking_uses_dense_ranking_without_skipping_places (test.TestEggTournament.test_ranking_uses_dense_ranking_without_skipping_places)
Looking up ranking positions should use dense ranking without skipping places.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 516, in test_ranking_uses_dense_ranking_without_skipping_places
self.assertEqual(3 @ tournament, {delta, epsilon})
~~^~~~~~~~~~~~
File "/tmp/solution.py", line 172, in __rmatmul__
raise IndexError
IndexError
======================================================================
FAIL: test_unpainted_half_loses_to_half_painted_black (test.TestEgg.test_unpainted_half_loses_to_half_painted_black)
An unpainted half should lose to a half painted in black.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 205, in test_unpainted_half_loses_to_half_painted_black
self.assertIs(black_egg * unpainted_egg, black_egg)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: <solution.Egg object at 0x72ee00890ec0> is not <solution.Egg object at 0x72ee00890d70>
======================================================================
FAIL: test_ranking_missing_position_raises_index_error (test.TestEggTournament.test_ranking_missing_position_raises_index_error)
Looking up a missing ranking position should raise an IndexError.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 538, in test_ranking_missing_position_raises_index_error
with self.assertRaises(IndexError):
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
AssertionError: IndexError not raised
----------------------------------------------------------------------
Ran 46 tests in 0.003s
FAILED (failures=2, errors=1)
| f | 1 | class Egg: | f | 1 | class Egg: |
| 2 | def __init__(self): | 2 | def __init__(self): | ||
| 3 | self._segments = [] | 3 | self._segments = [] | ||
| 4 | self._filled = 0.0 | 4 | self._filled = 0.0 | ||
| 5 | self._broken_top = False | 5 | self._broken_top = False | ||
| 6 | self._broken_bottom = False | 6 | self._broken_bottom = False | ||
| 7 | self._tournament = None | 7 | self._tournament = None | ||
| 8 | 8 | ||||
| 9 | def paint(self, *args): | 9 | def paint(self, *args): | ||
| 10 | total_new = sum(p for _, p in args) | 10 | total_new = sum(p for _, p in args) | ||
| 11 | 11 | ||||
| 12 | if self._filled + total_new > 100.0: | 12 | if self._filled + total_new > 100.0: | ||
| 13 | raise ValueError("Too much paint") | 13 | raise ValueError("Too much paint") | ||
| 14 | 14 | ||||
| 15 | current = self._filled | 15 | current = self._filled | ||
| 16 | 16 | ||||
| 17 | for hex_color, percentage in args: | 17 | for hex_color, percentage in args: | ||
| 18 | pigment = self._pigment(hex_color) | 18 | pigment = self._pigment(hex_color) | ||
| 19 | 19 | ||||
| 20 | start = current | 20 | start = current | ||
| 21 | end = current + percentage | 21 | end = current + percentage | ||
| 22 | 22 | ||||
| 23 | if start < 50 < end: | 23 | if start < 50 < end: | ||
| 24 | self._segments.append((start, 50, pigment)) | 24 | self._segments.append((start, 50, pigment)) | ||
| 25 | self._segments.append((50, end, pigment)) | 25 | self._segments.append((50, end, pigment)) | ||
| 26 | else: | 26 | else: | ||
| 27 | self._segments.append((start, end, pigment)) | 27 | self._segments.append((start, end, pigment)) | ||
| 28 | 28 | ||||
| 29 | current = end | 29 | current = end | ||
| 30 | 30 | ||||
| 31 | self._filled += total_new | 31 | self._filled += total_new | ||
| 32 | 32 | ||||
| 33 | def _pigment(self, hex_color): | 33 | def _pigment(self, hex_color): | ||
| n | 34 | hex_color = hex_color.lower() | n | ||
| 35 | r = int(hex_color[0:2], 16) | 34 | r = int(hex_color[0:2], 16) | ||
| 36 | g = int(hex_color[2:4], 16) | 35 | g = int(hex_color[2:4], 16) | ||
| 37 | b = int(hex_color[4:6], 16) | 36 | b = int(hex_color[4:6], 16) | ||
| 38 | return r + g + b | 37 | return r + g + b | ||
| 39 | 38 | ||||
| n | 40 | def _strength(self, top=True): | n | 39 | def _strength(self, top): |
| 41 | if top and self._broken_top: | 40 | if top and self._broken_top: | ||
| 42 | raise TypeError("Top side is broken") | 41 | raise TypeError("Top side is broken") | ||
| 43 | if not top and self._broken_bottom: | 42 | if not top and self._broken_bottom: | ||
| 44 | raise TypeError("Bottom side is broken") | 43 | raise TypeError("Bottom side is broken") | ||
| 45 | 44 | ||||
| 46 | low, high = (0, 50) if top else (50, 100) | 45 | low, high = (0, 50) if top else (50, 100) | ||
| n | 47 | n | |||
| 48 | total = 0.0 | 46 | total = 0.0 | ||
| 49 | 47 | ||||
| 50 | for start, end, pigment in self._segments: | 48 | for start, end, pigment in self._segments: | ||
| 51 | overlap_start = max(start, low) | 49 | overlap_start = max(start, low) | ||
| 52 | overlap_end = min(end, high) | 50 | overlap_end = min(end, high) | ||
| 53 | 51 | ||||
| 54 | if overlap_start < overlap_end: | 52 | if overlap_start < overlap_end: | ||
| 55 | portion = (overlap_end - overlap_start) / 50 | 53 | portion = (overlap_end - overlap_start) / 50 | ||
| 56 | total += portion * pigment | 54 | total += portion * pigment | ||
| 57 | 55 | ||||
| 58 | return total | 56 | return total | ||
| 59 | 57 | ||||
| n | 60 | def _battle(self, other, top=True): | n | 58 | def _battle(self, other, top): |
| 61 | s1 = self._strength(top) | 59 | s1 = self._strength(top) | ||
| 62 | s2 = other._strength(top) | 60 | s2 = other._strength(top) | ||
| 63 | 61 | ||||
| 64 | if s1 > s2: | 62 | if s1 > s2: | ||
| 65 | winner, loser = self, other | 63 | winner, loser = self, other | ||
| 66 | else: | 64 | else: | ||
| 67 | winner, loser = other, self | 65 | winner, loser = other, self | ||
| 68 | 66 | ||||
| 69 | if top: | 67 | if top: | ||
| 70 | loser._broken_top = True | 68 | loser._broken_top = True | ||
| 71 | else: | 69 | else: | ||
| 72 | loser._broken_bottom = True | 70 | loser._broken_bottom = True | ||
| 73 | 71 | ||||
| 74 | if self._tournament and self._tournament is other._tournament: | 72 | if self._tournament and self._tournament is other._tournament: | ||
| n | 75 | self._tournament._record(self, other, "top" if top else "bottom", winner) | n | 73 | self._tournament.record(self, other, "top" if top else "bottom", winner) |
| 76 | 74 | ||||
| 77 | return winner | 75 | return winner | ||
| 78 | 76 | ||||
| 79 | def __mul__(self, other): | 77 | def __mul__(self, other): | ||
| n | 80 | return self._battle(other, top=True) | n | 78 | return self._battle(other, True) |
| 81 | 79 | ||||
| 82 | def __matmul__(self, other): | 80 | def __matmul__(self, other): | ||
| n | 83 | return self._battle(other, top=False) | n | 81 | return self._battle(other, False) |
| 84 | 82 | ||||
| 85 | 83 | ||||
| 86 | class EggTournament: | 84 | class EggTournament: | ||
| 87 | def __init__(self): | 85 | def __init__(self): | ||
| 88 | self._eggs = {} | 86 | self._eggs = {} | ||
| 89 | self._names = {} | 87 | self._names = {} | ||
| 90 | self._history = {} | 88 | self._history = {} | ||
| 91 | self._wins = {} | 89 | self._wins = {} | ||
| 92 | 90 | ||||
| 93 | def register(self, egg, name): | 91 | def register(self, egg, name): | ||
| 94 | if not name.isidentifier(): | 92 | if not name.isidentifier(): | ||
| 95 | raise ValueError("Invalid registration name") | 93 | raise ValueError("Invalid registration name") | ||
| 96 | 94 | ||||
| 97 | if name in self._eggs: | 95 | if name in self._eggs: | ||
| 98 | raise ValueError(f"Egg with name {name} has already been registered") | 96 | raise ValueError(f"Egg with name {name} has already been registered") | ||
| 99 | 97 | ||||
| 100 | if egg._tournament is not None: | 98 | if egg._tournament is not None: | ||
| 101 | raise ValueError("An egg cannot be registered in multiple tournaments") | 99 | raise ValueError("An egg cannot be registered in multiple tournaments") | ||
| 102 | 100 | ||||
| 103 | self._eggs[name] = egg | 101 | self._eggs[name] = egg | ||
| 104 | self._names[egg] = name | 102 | self._names[egg] = name | ||
| 105 | self._wins[egg] = 0 | 103 | self._wins[egg] = 0 | ||
| 106 | egg._tournament = self | 104 | egg._tournament = self | ||
| 107 | 105 | ||||
| 108 | def _pair_key(self, egg1, egg2): | 106 | def _pair_key(self, egg1, egg2): | ||
| 109 | return (egg1, egg2) if id(egg1) < id(egg2) else (egg2, egg1) | 107 | return (egg1, egg2) if id(egg1) < id(egg2) else (egg2, egg1) | ||
| 110 | 108 | ||||
| n | 111 | def _record(self, egg1, egg2, side, winner): | n | 109 | def record(self, egg1, egg2, side, winner): |
| 112 | if egg1 not in self._names or egg2 not in self._names: | 110 | if egg1 not in self._names or egg2 not in self._names: | ||
| 113 | return | 111 | return | ||
| 114 | 112 | ||||
| 115 | key = self._pair_key(egg1, egg2), side | 113 | key = self._pair_key(egg1, egg2), side | ||
| 116 | self._history[key] = winner | 114 | self._history[key] = winner | ||
| 117 | self._wins[winner] += 1 | 115 | self._wins[winner] += 1 | ||
| 118 | 116 | ||||
| 119 | def __getitem__(self, item): | 117 | def __getitem__(self, item): | ||
| 120 | if isinstance(item, tuple): | 118 | if isinstance(item, tuple): | ||
| 121 | egg1, egg2, side = item | 119 | egg1, egg2, side = item | ||
| 122 | elif isinstance(item, slice): | 120 | elif isinstance(item, slice): | ||
| 123 | egg1, egg2, side = item.start, item.stop, item.step | 121 | egg1, egg2, side = item.start, item.stop, item.step | ||
| 124 | else: | 122 | else: | ||
| 125 | raise KeyError | 123 | raise KeyError | ||
| 126 | 124 | ||||
| 127 | key = self._pair_key(egg1, egg2), side | 125 | key = self._pair_key(egg1, egg2), side | ||
| 128 | 126 | ||||
| 129 | if key not in self._history: | 127 | if key not in self._history: | ||
| 130 | raise KeyError | 128 | raise KeyError | ||
| 131 | 129 | ||||
| 132 | return self._history[key] | 130 | return self._history[key] | ||
| 133 | 131 | ||||
| 134 | def __contains__(self, egg): | 132 | def __contains__(self, egg): | ||
| 135 | return egg in self._names | 133 | return egg in self._names | ||
| 136 | 134 | ||||
| 137 | def __getattr__(self, name): | 135 | def __getattr__(self, name): | ||
| 138 | if name not in self._eggs: | 136 | if name not in self._eggs: | ||
| 139 | raise AttributeError("Apologies, there is no such egg registered") | 137 | raise AttributeError("Apologies, there is no such egg registered") | ||
| 140 | 138 | ||||
| 141 | egg = self._eggs[name] | 139 | egg = self._eggs[name] | ||
| n | 142 | victories = self._wins[egg] | n | 140 | return { |
| 143 | position = self._position(egg) | 141 | "position": self._position(egg), | ||
| 142 | "victories": self._wins[egg] | ||||
| 143 | } | ||||
| 144 | 144 | ||||
| n | 145 | return {"position": position, "victories": victories} | n | 145 | @property |
| 146 | |||||
| 147 | def _ranking(self): | 146 | def ranking(self): | ||
| 148 | sorted_eggs = sorted(self._wins.items(), key=lambda x: -x[1]) | 147 | sorted_eggs = sorted(self._wins.items(), key=lambda x: -x[1]) | ||
| 149 | 148 | ||||
| 150 | ranking = {} | 149 | ranking = {} | ||
| 151 | current_pos = 1 | 150 | current_pos = 1 | ||
| 152 | last_wins = None | 151 | last_wins = None | ||
| 153 | 152 | ||||
| 154 | for i, (egg, wins) in enumerate(sorted_eggs): | 153 | for i, (egg, wins) in enumerate(sorted_eggs): | ||
| 155 | if wins != last_wins: | 154 | if wins != last_wins: | ||
| 156 | current_pos = i + 1 | 155 | current_pos = i + 1 | ||
| n | n | 156 | |||
| 157 | if current_pos not in ranking: | 157 | if current_pos not in ranking: | ||
| 158 | ranking[current_pos] = set() | 158 | ranking[current_pos] = set() | ||
| 159 | 159 | ||||
| 160 | ranking[current_pos].add(egg) | 160 | ranking[current_pos].add(egg) | ||
| 161 | last_wins = wins | 161 | last_wins = wins | ||
| 162 | 162 | ||||
| 163 | return ranking | 163 | return ranking | ||
| 164 | 164 | ||||
| 165 | def _position(self, egg): | 165 | def _position(self, egg): | ||
| n | 166 | ranking = self._ranking() | n | ||
| 167 | for pos, eggs in ranking.items(): | 166 | for pos, eggs in self.ranking.items(): | ||
| 168 | if egg in eggs: | 167 | if egg in eggs: | ||
| 169 | return pos | 168 | return pos | ||
| 170 | 169 | ||||
| 171 | def __rmatmul__(self, position): | 170 | def __rmatmul__(self, position): | ||
| n | 172 | ranking = self._ranking() | n | ||
| 173 | |||||
| 174 | if position not in ranking: | 171 | if position not in self.ranking: | ||
| 175 | raise IndexError | 172 | raise IndexError | ||
| 176 | 173 | ||||
| n | 177 | eggs = ranking[position] | n | 174 | eggs = self.ranking[position] |
| 178 | 175 | ||||
| 179 | if len(eggs) == 1: | 176 | if len(eggs) == 1: | ||
| 180 | return list(eggs)[0] | 177 | return list(eggs)[0] | ||
| n | n | 178 | |||
| 181 | return eggs | 179 | return eggs | ||
| t | 182 | t | |||
| 183 |
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
| |||||||||