Домашни > Великденско домашно > Решения > Решението на Николай Георгиев

Резултати
5 точки от тестове
0 точки от учител

5 точки общо

42 успешни теста
4 неуспешни теста
Код
Скрий всички коментари

  1import re
  2import keyword
  3
  4class Egg:
  5    _tournament = None
  6
  7    def __init__(self):
  8        self._colors = []   
  9        self._total = 0.0
 10        self._broken_top = False
 11        self._broken_bottom = False
 12
 13    def paint(self, *pairs):
 14        new_total = sum(p for _, p in pairs)
 15        if self._total + new_total > 100.0:
 16            raise ValueError("Cannot overfill the egg")
 17        for hex_color, percentage in pairs:
 18            self._colors.append((hex_color.upper(), percentage))
 19            self._total += percentage
 20
 21    def _pigment(self, side):
 22        """Return total pigment for 'top' (0-50%) or 'bottom' (50-100%) half."""
 23        if side not in ("top", "bottom"):
 24            raise ValueError("side must be 'top' or 'bottom'")
 25        lower = 0.0 if side == "top" else 50.0
 26        upper = 50.0 if side == "top" else 100.0
 27
 28        total_pigment = 0.0
 29        cumulative = 0.0
 30        for hex_color, percentage in self._colors:
 31            seg_start = cumulative
 32            seg_end   = cumulative + percentage
 33            overlap = max(0.0, min(seg_end, upper) - max(seg_start, lower))
 34            if overlap > 0:
 35                r = int(hex_color[0:2], 16)
 36                g = int(hex_color[2:4], 16)
 37                b = int(hex_color[4:6], 16)
 38                pig_per_pct = (r + g + b) / percentage
 39                total_pigment += overlap * pig_per_pct
 40            cumulative += percentage
 41        return total_pigment
 42
 43    def _clash(self, other, side):
 44        if side == "top":
 45            if self._broken_top:
 46                raise TypeError("This egg's top is already broken")
 47            if other._broken_top:
 48                raise TypeError("The other egg's top is already broken")
 49        else:
 50            if self._broken_bottom:
 51                raise TypeError("This egg's bottom is already broken")
 52            if other._broken_bottom:
 53                raise TypeError("The other egg's bottom is already broken")
 54
 55        my_pig    = self._pigment(side)
 56        their_pig = other._pigment(side)
 57
 58        if my_pig >= their_pig:
 59            winner, loser = self, other
 60        else:
 61            winner, loser = other, self
 62
 63        if side == "top":
 64            loser._broken_top = True
 65        else:
 66            loser._broken_bottom = True
 67
 68        if self._tournament is not None:
 69            self._tournament._record(self, other, side, winner)
 70
 71        return winner
 72
 73    def __mul__(self, other):
 74        return self._clash(other, "top")
 75
 76    def __matmul__(self, other):
 77        return self._clash(other, "bottom")
 78    
 79
 80class EggTournament:
 81    def __init__(self):
 82        self._eggs   = {}   # name -> egg
 83        self._wins   = {}   # egg  -> int
 84        self._history = {}
 85
 86    def register(self, egg, name):
 87        if not name.isidentifier() or keyword.iskeyword(name):
 88            raise ValueError("Invalid registration name")
 89        if egg._tournament is not None:
 90            raise ValueError("An egg cannot be registered in multiple tournaments")
 91        if name in self._eggs:
 92            raise ValueError(f"Egg with name {name} has already been registered")
 93        
 94        self._eggs[name] = egg
 95        self._wins[egg]  = 0
 96        egg._tournament  = self
 97
 98    def _record(self, egg_a, egg_b, side, winner):
 99        """Only care about clashes between two registered eggs."""
100        reg = set(self._eggs.values())
101        if egg_a not in reg or egg_b not in reg:
102            return
103        key = frozenset({egg_a, egg_b})
104        if key not in self._history:
105            self._history[key] = {}
106        self._history[key][side] = winner
107        self._wins[winner] += 1
108
109    def _ranking(self):
110        """Return dict: egg -> rank (dense/no-skip)."""
111        eggs = list(self._wins.keys())
112        sorted_eggs = sorted(eggs, key=lambda e: self._wins[e], reverse=True)
113        rank_map = {}
114        rank = 1
115        i = 0
116        while i < len(sorted_eggs):
117            j = i
118            while j < len(sorted_eggs) and \
119                  self._wins[sorted_eggs[j]] == self._wins[sorted_eggs[i]]:
120                j += 1
121            for e in sorted_eggs[i:j]:
122                rank_map[e] = rank
123            rank += 1
124            i = j
125        return rank_map
126
127    def __getitem__(self, key):
128        if isinstance(key, slice):
129            egg_a = key.start
130            egg_b = key.stop
131            side_str = key.step
132        elif isinstance(key, tuple) and len(key) == 3:
133            egg_a, egg_b, side_str = key
134        else:
135            raise KeyError(key)
136
137        side = "top" if side_str == "top" else "bottom"
138        k = frozenset({egg_a, egg_b})
139        if k not in self._history or side not in self._history[k]:
140            raise KeyError("No such clash recorded")
141        return self._history[k][side]
142
143    def __rmatmul__(self, position):
144        rank_map = self._ranking()
145        at_pos = {e for e, r in rank_map.items() if r == position}
146        if not at_pos:
147            raise IndexError(f"No egg at position {position}")
148        if len(at_pos) == 1:
149            return next(iter(at_pos))
150        return at_pos
151
152    def __getattr__(self, name):
153        """Called only when normal lookup fails"""
154        eggs = object.__getattribute__(self, "_eggs")
155        if name in eggs:
156            egg = eggs[name]
157            rank_map = self._ranking()
158            wins = object.__getattribute__(self, "_wins")
159            return {"position": rank_map[egg], "victories": wins[egg]}
160        raise AttributeError("Apologies, there is no such egg registered")
161
162    def __contains__(self, egg):
163        return egg in self._eggs.values()

.........F..F....F...F........................
======================================================================
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 0x728945038d70> is not <solution.Egg object at 0x728945038e50>

======================================================================
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 0x728945038d70> is not <solution.Egg object at 0x728945038e50>

======================================================================
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 0x728945038d70> is not <solution.Egg object at 0x728945038e50>

======================================================================
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 200, in test_unpainted_half_loses_to_half_painted_black
self.assertIs(unpainted_egg * black_egg, black_egg)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: <solution.Egg object at 0x728945038d70> is not <solution.Egg object at 0x728945038e50>

----------------------------------------------------------------------
Ran 46 tests in 0.003s

FAILED (failures=4)

Дискусия
История
Това решение има само една версия.