1FULL_PERCENTAGE = 100
2HALF_PERCENTAGE = 50
3NUMBER_SYSTEM = 16
4COLOR_LEN = 2
5
6class Egg:
7
8 def __init__(self):
9 self.painted_percentage = 0
10 self.paints = []
11 self.solid_upper_side = True
12 self.solid_lower_side = True
13 self.tournament = None
14
15 def paint(self, *args):
16 total = 0
17 for _, percentage in args:
18 total += percentage
19 if self.painted_percentage + total > FULL_PERCENTAGE:
20 raise ValueError
21 for hex_color, percentage in args:
22 self.painted_percentage += percentage
23 self.paints.append((hex_color, percentage))
24
25 def _pigment(self, color):
26 r = int(color[0:COLOR_LEN], NUMBER_SYSTEM)
27 g = int(color[COLOR_LEN:COLOR_LEN * 2], NUMBER_SYSTEM)
28 b = int(color[COLOR_LEN * 2:COLOR_LEN * 3], NUMBER_SYSTEM)
29 return r + g + b
30
31 def _strength(self, part):
32 needed_start = 0 if part == "top" else HALF_PERCENTAGE
33 needed_end = HALF_PERCENTAGE if part == "top" else FULL_PERCENTAGE
34 current = 0
35 total_strength = 0
36
37 for color, p in self.paints:
38 start = current
39 end = current + p
40
41 overlap = max(0, min(end, needed_end) - max(start, needed_start))
42
43 if overlap > 0:
44 ratio = overlap / p
45 total_strength += self._pigment(color) * ratio
46
47 current += p
48
49 return total_strength
50
51 def _fight(self, other, part):
52 if part == "top":
53 if not self.solid_upper_side or not other.solid_upper_side:
54 raise TypeError("Already broken side")
55 else:
56 if not self.solid_lower_side or not other.solid_lower_side:
57 raise TypeError("Already broken side")
58
59 my_strength = self._strength(part)
60 other_strength = other._strength(part)
61
62 if my_strength > other_strength:
63 winner, loser = self, other
64 else:
65 winner, loser = other, self
66
67 if part == "top":
68 loser.solid_upper_side = False
69 else:
70 loser.solid_lower_side = False
71
72 if self.tournament and self.tournament is other.tournament:
73 self.tournament._record(self, other, part, winner)
74
75 return winner
76
77 def __mul__(self, other):
78 return self._fight(other, "top")
79
80
81 def __matmul__(self, other):
82 return self._fight(other, "bottom")
83
84class EggTournament:
85
86 def __init__(self):
87 self.collisions = {}
88 self.egg_names = {}
89 self.wins = {}
90
91 def register(self, egg, name):
92 if egg.tournament:
93 raise ValueError("An egg cannot be registered in multiple tournaments")
94 if not name.isidentifier():
95 raise ValueError("Invalid registration name")
96 if name in self.egg_names:
97 raise ValueError(f"Egg with name {name} has already been registered")
98 self.egg_names[name] = egg
99 self.wins[egg] = 0
100 egg.tournament = self
101
102 def _record(self, egg1, egg2, part, winner):
103 key = frozenset([egg1, egg2]), part
104 self.collisions[key] = winner
105 self.wins[winner] += 1
106
107 def __getitem__(self, key):
108 if isinstance(key, tuple):
109 egg1, egg2, part = key
110 else:
111 egg1, egg2, part = key.start, key.stop, key.step
112
113 k = frozenset([egg1, egg2]), part
114
115 if k not in self.collisions:
116 raise KeyError
117
118 return self.collisions[k]
119
120 def _ranking(self):
121 sorted_eggs = sorted(self.wins.items(), key=lambda x: -x[1])
122
123 ranking = {}
124 pos = 1
125 last_score = None
126
127 for i, (egg, score) in enumerate(sorted_eggs):
128 if score != last_score:
129 pos = i + 1
130 if pos not in ranking:
131 ranking[pos] = set()
132 ranking[pos].add(egg)
133 last_score = score
134
135 return ranking
136
137 def __getattr__(self, name):
138 if name not in self.egg_names:
139 raise AttributeError("Apologies, there is no such egg registered")
140
141 egg = self.egg_names[name]
142 wins = self.wins[egg]
143
144 ranking = self._ranking()
145
146 position = next(pos for pos, eggs in ranking.items() if egg in eggs)
147
148 return {"position": position, "victories": wins}
149
150 def __contains__(self, egg):
151 return egg in self.wins
152
153 def __rmatmul__(self, position):
154 ranking = self._ranking()
155
156 if position not in ranking:
157 raise IndexError
158
159 result = ranking[position]
160 return next(iter(result)) if len(result) == 1 else result
.........F..F....F...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 157, in __rmatmul__
raise IndexError
IndexError
======================================================================
FAIL: test_paint_chunk_crossing_50_percent_boundary_is_split_correctly (test.TestEgg.test_paint_chunk_crossing_50_percent_boundary_is_split_correctly)
Painting a chunk across the half boundary should split it correctly between the two halves.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 139, in test_paint_chunk_crossing_50_percent_boundary_is_split_correctly
self.assertIs(egg @ opponent, opponent)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: <solution.Egg object at 0x72d5d8e4cd00> is not <solution.Egg object at 0x72d5d8e4cde0>
======================================================================
FAIL: test_paint_multiple_calls_can_cross_50_percent_boundary (test.TestEgg.test_paint_multiple_calls_can_cross_50_percent_boundary)
Painting an egg across the half boundary in multiple calls should split the halves correctly.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 151, in test_paint_multiple_calls_can_cross_50_percent_boundary
self.assertIs(egg @ opponent, opponent)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: <solution.Egg object at 0x72d5d8e4cd00> is not <solution.Egg object at 0x72d5d8e4cde0>
======================================================================
FAIL: test_paint_overflow_with_multiple_colors_should_not_change_the_egg (test.TestEgg.test_paint_overflow_with_multiple_colors_should_not_change_the_egg)
Failing to overpaint an egg with multiple colors should not change the egg.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 163, in test_paint_overflow_with_multiple_colors_should_not_change_the_egg
self.assertIs(egg @ bottom_opponent, bottom_opponent)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: <solution.Egg object at 0x72d5d8e4cd00> is not <solution.Egg object at 0x72d5d8e4cde0>
======================================================================
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 0x72d5d8e4ce50> is not <solution.Egg object at 0x72d5d8e4cd00>
======================================================================
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=5, errors=1)
Виктор Бечев
16.04.2026 19:01Отвъд дребните коментари - решението е супер.
|
16.04.2026 18:52
16.04.2026 18:54
16.04.2026 18:59
16.04.2026 19:00