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

Резултати
6 точки от тестове
1 точки от учител

7 точки общо

46 успешни теста
0 неуспешни теста
Код

  1import keyword
  2from enum import Enum
  3from typing import Protocol
  4
  5EGG_SIDE_SIZE = EGG_SIDE_BOUNDARY = 50.0
  6MAX_FILL = 100.0
  7RGB_PAIR_COUNT = 3
  8HEX_BASE = 16
  9
 10
 11class Side(Enum):
 12    TOP = (0.0, EGG_SIDE_BOUNDARY)
 13    BOTTOM = (EGG_SIDE_BOUNDARY, MAX_FILL)
 14
 15    def __init__(self, low, high):
 16        self.low = low
 17        self.high = high
 18
 19    @classmethod
 20    def from_str(cls, s):
 21        try:
 22            return cls[s.upper()]
 23        except KeyError:
 24            raise KeyError(f"Side must be one of {[m.lower() for m in cls._member_names_]}")
 25
 26
 27class CollisionObserver(Protocol):
 28    def __call__(self, e1: "Egg", e2: "Egg", side: Side, winner: "Egg") -> None:
 29        pass
 30
 31
 32class PaintLayer:
 33    def __init__(self, rgb, fill_start, fill_end):
 34        self.rgb = rgb
 35        self.fill_start = fill_start
 36        self.fill_end = fill_end
 37
 38    def overlap_with(self, side):
 39        return min(self.fill_end, side.high) - max(self.fill_start, side.low)
 40
 41    def pigment_for(self, side):
 42        overlap = self.overlap_with(side)
 43        return self.rgb * (max(overlap, 0) / EGG_SIDE_SIZE)
 44
 45
 46class EggSide:
 47    def __init__(self):
 48        self.__pigment = 0.0
 49        self.__has_paint = False
 50        self.__broken = False
 51
 52    def add_pigment(self, amount):
 53        self.__has_paint = True
 54        self.__pigment += amount
 55
 56    def is_stronger_than(self, other):
 57        if self.__has_paint and not other.__has_paint:
 58            return True
 59        if other.__has_paint and not self.__has_paint:
 60            return False
 61        return self.__pigment >= other.__pigment
 62
 63    @property
 64    def broken(self):
 65        return self.__broken
 66
 67    def smash(self):
 68        self.__broken = True
 69
 70
 71class Egg:
 72    def __init__(self):
 73        self.__sides = {s: EggSide() for s in Side}
 74        self.__paint_level = 0.0
 75        self.__observer: CollisionObserver | None = None
 76
 77    def __mul__(self, other):
 78        return self._collide(other, Side.TOP)
 79
 80    def __matmul__(self, other):
 81        return self._collide(other, Side.BOTTOM)
 82
 83    def paint(self, *args):
 84        if self.__paint_level + sum(amount for _, amount in args) > MAX_FILL:
 85            raise ValueError("Paint overfill!")
 86
 87        for color, amount in args:
 88            layer = PaintLayer(_parse_rgb(color), self.__paint_level, self.__paint_level + amount)
 89            for side in Side:
 90                if layer.overlap_with(side) > 0:
 91                    self.__sides[side].add_pigment(layer.pigment_for(side))
 92            self.__paint_level += amount
 93
 94    def set_observer(self, observer: CollisionObserver):
 95        if self.__observer is not None:
 96            raise ValueError("An egg cannot be registered in multiple tournaments")
 97        self.__observer = observer
 98
 99    def _collide(self, other, side):
100        if self.__sides[side].broken or other.__sides[side].broken:
101            raise TypeError("Cannot collide on a broken side")
102
103        # Battle
104        winner, loser = self._resolve_collision(other, side)
105        loser.__sides[side].smash()
106
107        # Notify
108        if self.__observer:
109            self.__observer(self, other, side, winner)
110
111        return winner
112
113    def _resolve_collision(self, other, side):
114        my_side = self.__sides[side]
115        their_side = other.__sides[side]
116        return (self, other) if my_side.is_stronger_than(their_side) else (other, self)
117
118
119def _parse_rgb(color):
120    return sum(int(color[i * 2: i * 2 + 2], HEX_BASE) for i in range(RGB_PAIR_COUNT))
121
122
123class EggTournament:
124    def __init__(self):
125        # Eggs
126        self.__eggs_by_name: dict[str, Egg] = {}
127        self.__registered_eggs: set[Egg] = set()
128
129        # Ranking
130        self.__wins: dict[Egg, int] = {}
131        self.__ranking_cache = None
132
133        # History
134        self.__battles: dict[tuple[frozenset, Side], Egg] = {}
135
136    def __getitem__(self, key):
137        if isinstance(key, slice):
138            e1, e2, side_str = key.start, key.stop, key.step
139        elif isinstance(key, tuple) and len(key) == 3:
140            e1, e2, side_str = key
141        else:
142            raise KeyError("Use tournament[e1, e2, side] or tournament[e1:e2:side]")
143
144        side = Side.from_str(side_str)
145        battle_key = (frozenset({e1, e2}), side)
146
147        if battle_key not in self.__battles:
148            raise KeyError("No such battle recorded")
149
150        return self.__battles[battle_key]
151
152    def __rmatmul__(self, position):
153        if not self.__wins:
154            raise IndexError("No eggs registered")
155
156        unique_sorted = self._sorted_unique_wins()
157        if position < 1 or position > len(unique_sorted):
158            raise IndexError(f"Position {position} does not exist in the ranking")
159
160        target = unique_sorted[position - 1]
161        result = {egg for egg, wins in self.__wins.items() if wins == target}
162        return result.pop() if len(result) == 1 else result
163
164    def __getattr__(self, name):
165        if name in self.__eggs_by_name:
166            egg = self.__eggs_by_name[name]
167            victories = self.__wins[egg]
168            position = self._sorted_unique_wins().index(victories) + 1
169            return {"position": position, "victories": victories}
170
171        raise AttributeError("Apologies, there is no such egg registered")
172
173    def __contains__(self, egg):
174        return egg in self.__registered_eggs
175
176    def register(self, egg, name):
177        if not name.isidentifier() or keyword.iskeyword(name):
178            raise ValueError("Invalid registration name")
179        if name in self.__eggs_by_name:
180            raise ValueError(f"Egg with name {name} has already been registered")
181        if egg in self.__registered_eggs:
182            raise ValueError("An egg cannot be registered in multiple tournaments")
183
184        egg.set_observer(self._record_battle)
185        self.__eggs_by_name[name] = egg
186        self.__registered_eggs.add(egg)
187        self.__wins[egg] = 0
188
189    def _record_battle(self, e1, e2, side, winner):
190        if e1 not in self.__registered_eggs or e2 not in self.__registered_eggs:
191            return
192
193        key = (frozenset({e1, e2}), side)
194        if key in self.__battles:
195            self.__wins[self.__battles[key]] -= 1
196
197        self.__battles[key] = winner
198        self.__wins[winner] += 1
199        self.__ranking_cache = None
200
201    def _sorted_unique_wins(self):
202        if self.__ranking_cache is None:
203            self.__ranking_cache = sorted(set(self.__wins.values()), reverse=True)
204        return self.__ranking_cache

..............................................
----------------------------------------------------------------------
Ran 46 tests in 0.002s

OK

Дискусия
Илиян Гаврилов
13.04.2026 17:17

Еми доста време мъчих Egg класа и беше ужасно объркващо, та като абсолютен минимум да се разпредели малко сложна логика, мисля, че поне PaintLayer класът беше нужен като абстракция
Виктор Бечев
12.04.2026 22:08

Интересни решения за някои от нещата. С един от преподавателите си говорихме за това дали си заслужава да се правят обекти за цветовете, ти си направил и за слоевете. На ръба между ненужно сложно и добър дизайн, но на мен по-скоро ми допада.
История

f1import keywordf1import keyword
2from enum import Enum2from enum import Enum
3from typing import Protocol3from typing import Protocol
44
5EGG_SIDE_SIZE = EGG_SIDE_BOUNDARY = 50.05EGG_SIDE_SIZE = EGG_SIDE_BOUNDARY = 50.0
6MAX_FILL = 100.06MAX_FILL = 100.0
7RGB_PAIR_COUNT = 37RGB_PAIR_COUNT = 3
8HEX_BASE = 168HEX_BASE = 16
99
1010
11class Side(Enum):11class Side(Enum):
12    TOP = (0.0, EGG_SIDE_BOUNDARY)12    TOP = (0.0, EGG_SIDE_BOUNDARY)
13    BOTTOM = (EGG_SIDE_BOUNDARY, MAX_FILL)13    BOTTOM = (EGG_SIDE_BOUNDARY, MAX_FILL)
1414
15    def __init__(self, low, high):15    def __init__(self, low, high):
16        self.low = low16        self.low = low
17        self.high = high17        self.high = high
1818
19    @classmethod19    @classmethod
20    def from_str(cls, s):20    def from_str(cls, s):
21        try:21        try:
22            return cls[s.upper()]22            return cls[s.upper()]
23        except KeyError:23        except KeyError:
24            raise KeyError(f"Side must be one of {[m.lower() for m in cls._member_names_]}")24            raise KeyError(f"Side must be one of {[m.lower() for m in cls._member_names_]}")
2525
2626
27class CollisionObserver(Protocol):27class CollisionObserver(Protocol):
28    def __call__(self, e1: "Egg", e2: "Egg", side: Side, winner: "Egg") -> None:28    def __call__(self, e1: "Egg", e2: "Egg", side: Side, winner: "Egg") -> None:
29        pass29        pass
3030
3131
32class PaintLayer:32class PaintLayer:
33    def __init__(self, rgb, fill_start, fill_end):33    def __init__(self, rgb, fill_start, fill_end):
34        self.rgb = rgb34        self.rgb = rgb
35        self.fill_start = fill_start35        self.fill_start = fill_start
36        self.fill_end = fill_end36        self.fill_end = fill_end
3737
38    def overlap_with(self, side):38    def overlap_with(self, side):
39        return min(self.fill_end, side.high) - max(self.fill_start, side.low)39        return min(self.fill_end, side.high) - max(self.fill_start, side.low)
4040
41    def pigment_for(self, side):41    def pigment_for(self, side):
42        overlap = self.overlap_with(side)42        overlap = self.overlap_with(side)
n43        return self.rgb * (overlap / EGG_SIDE_SIZE) if overlap > 0 else 0.0n43        return self.rgb * (max(overlap, 0) / EGG_SIDE_SIZE)
4444
4545
46class EggSide:46class EggSide:
47    def __init__(self):47    def __init__(self):
48        self.__pigment = 0.048        self.__pigment = 0.0
49        self.__has_paint = False49        self.__has_paint = False
50        self.__broken = False50        self.__broken = False
5151
52    def add_pigment(self, amount):52    def add_pigment(self, amount):
53        self.__has_paint = True53        self.__has_paint = True
54        self.__pigment += amount54        self.__pigment += amount
5555
56    def is_stronger_than(self, other):56    def is_stronger_than(self, other):
57        if self.__has_paint and not other.__has_paint:57        if self.__has_paint and not other.__has_paint:
58            return True58            return True
59        if other.__has_paint and not self.__has_paint:59        if other.__has_paint and not self.__has_paint:
60            return False60            return False
61        return self.__pigment >= other.__pigment61        return self.__pigment >= other.__pigment
6262
63    @property63    @property
64    def broken(self):64    def broken(self):
65        return self.__broken65        return self.__broken
6666
67    def smash(self):67    def smash(self):
68        self.__broken = True68        self.__broken = True
6969
7070
71class Egg:71class Egg:
72    def __init__(self):72    def __init__(self):
73        self.__sides = {s: EggSide() for s in Side}73        self.__sides = {s: EggSide() for s in Side}
74        self.__paint_level = 0.074        self.__paint_level = 0.0
75        self.__observer: CollisionObserver | None = None75        self.__observer: CollisionObserver | None = None
7676
77    def __mul__(self, other):77    def __mul__(self, other):
78        return self._collide(other, Side.TOP)78        return self._collide(other, Side.TOP)
7979
80    def __matmul__(self, other):80    def __matmul__(self, other):
81        return self._collide(other, Side.BOTTOM)81        return self._collide(other, Side.BOTTOM)
8282
83    def paint(self, *args):83    def paint(self, *args):
84        if self.__paint_level + sum(amount for _, amount in args) > MAX_FILL:84        if self.__paint_level + sum(amount for _, amount in args) > MAX_FILL:
85            raise ValueError("Paint overfill!")85            raise ValueError("Paint overfill!")
8686
87        for color, amount in args:87        for color, amount in args:
88            layer = PaintLayer(_parse_rgb(color), self.__paint_level, self.__paint_level + amount)88            layer = PaintLayer(_parse_rgb(color), self.__paint_level, self.__paint_level + amount)
89            for side in Side:89            for side in Side:
90                if layer.overlap_with(side) > 0:90                if layer.overlap_with(side) > 0:
91                    self.__sides[side].add_pigment(layer.pigment_for(side))91                    self.__sides[side].add_pigment(layer.pigment_for(side))
92            self.__paint_level += amount92            self.__paint_level += amount
9393
94    def set_observer(self, observer: CollisionObserver):94    def set_observer(self, observer: CollisionObserver):
95        if self.__observer is not None:95        if self.__observer is not None:
96            raise ValueError("An egg cannot be registered in multiple tournaments")96            raise ValueError("An egg cannot be registered in multiple tournaments")
97        self.__observer = observer97        self.__observer = observer
9898
99    def _collide(self, other, side):99    def _collide(self, other, side):
100        if self.__sides[side].broken or other.__sides[side].broken:100        if self.__sides[side].broken or other.__sides[side].broken:
101            raise TypeError("Cannot collide on a broken side")101            raise TypeError("Cannot collide on a broken side")
102102
103        # Battle103        # Battle
104        winner, loser = self._resolve_collision(other, side)104        winner, loser = self._resolve_collision(other, side)
105        loser.__sides[side].smash()105        loser.__sides[side].smash()
106106
107        # Notify107        # Notify
108        if self.__observer:108        if self.__observer:
109            self.__observer(self, other, side, winner)109            self.__observer(self, other, side, winner)
110110
111        return winner111        return winner
112112
113    def _resolve_collision(self, other, side):113    def _resolve_collision(self, other, side):
114        my_side = self.__sides[side]114        my_side = self.__sides[side]
115        their_side = other.__sides[side]115        their_side = other.__sides[side]
116        return (self, other) if my_side.is_stronger_than(their_side) else (other, self)116        return (self, other) if my_side.is_stronger_than(their_side) else (other, self)
117117
118118
119def _parse_rgb(color):119def _parse_rgb(color):
n120    c = color.upper()n
121    return sum(int(c[i * 2: i * 2 + 2], HEX_BASE) for i in range(RGB_PAIR_COUNT))120    return sum(int(color[i * 2: i * 2 + 2], HEX_BASE) for i in range(RGB_PAIR_COUNT))
122121
123122
124class EggTournament:123class EggTournament:
125    def __init__(self):124    def __init__(self):
126        # Eggs125        # Eggs
127        self.__eggs_by_name: dict[str, Egg] = {}126        self.__eggs_by_name: dict[str, Egg] = {}
128        self.__registered_eggs: set[Egg] = set()127        self.__registered_eggs: set[Egg] = set()
129128
130        # Ranking129        # Ranking
131        self.__wins: dict[Egg, int] = {}130        self.__wins: dict[Egg, int] = {}
132        self.__ranking_cache = None131        self.__ranking_cache = None
133132
134        # History133        # History
135        self.__battles: dict[tuple[frozenset, Side], Egg] = {}134        self.__battles: dict[tuple[frozenset, Side], Egg] = {}
136135
137    def __getitem__(self, key):136    def __getitem__(self, key):
138        if isinstance(key, slice):137        if isinstance(key, slice):
139            e1, e2, side_str = key.start, key.stop, key.step138            e1, e2, side_str = key.start, key.stop, key.step
140        elif isinstance(key, tuple) and len(key) == 3:139        elif isinstance(key, tuple) and len(key) == 3:
141            e1, e2, side_str = key140            e1, e2, side_str = key
142        else:141        else:
143            raise KeyError("Use tournament[e1, e2, side] or tournament[e1:e2:side]")142            raise KeyError("Use tournament[e1, e2, side] or tournament[e1:e2:side]")
144143
145        side = Side.from_str(side_str)144        side = Side.from_str(side_str)
146        battle_key = (frozenset({e1, e2}), side)145        battle_key = (frozenset({e1, e2}), side)
147146
148        if battle_key not in self.__battles:147        if battle_key not in self.__battles:
149            raise KeyError("No such battle recorded")148            raise KeyError("No such battle recorded")
150149
151        return self.__battles[battle_key]150        return self.__battles[battle_key]
152151
153    def __rmatmul__(self, position):152    def __rmatmul__(self, position):
154        if not self.__wins:153        if not self.__wins:
155            raise IndexError("No eggs registered")154            raise IndexError("No eggs registered")
156155
157        unique_sorted = self._sorted_unique_wins()156        unique_sorted = self._sorted_unique_wins()
158        if position < 1 or position > len(unique_sorted):157        if position < 1 or position > len(unique_sorted):
159            raise IndexError(f"Position {position} does not exist in the ranking")158            raise IndexError(f"Position {position} does not exist in the ranking")
160159
161        target = unique_sorted[position - 1]160        target = unique_sorted[position - 1]
162        result = {egg for egg, wins in self.__wins.items() if wins == target}161        result = {egg for egg, wins in self.__wins.items() if wins == target}
163        return result.pop() if len(result) == 1 else result162        return result.pop() if len(result) == 1 else result
164163
165    def __getattr__(self, name):164    def __getattr__(self, name):
t166        eggs = object.__getattribute__(self, "_EggTournament__eggs_by_name")t
167        wins = object.__getattribute__(self, "_EggTournament__wins")
168 
169        if name in eggs:165        if name in self.__eggs_by_name:
170            egg = eggs[name]166            egg = self.__eggs_by_name[name]
171            victories = wins[egg]167            victories = self.__wins[egg]
172            position = self._sorted_unique_wins().index(victories) + 1168            position = self._sorted_unique_wins().index(victories) + 1
173            return {"position": position, "victories": victories}169            return {"position": position, "victories": victories}
174170
175        raise AttributeError("Apologies, there is no such egg registered")171        raise AttributeError("Apologies, there is no such egg registered")
176172
177    def __contains__(self, egg):173    def __contains__(self, egg):
178        return egg in self.__registered_eggs174        return egg in self.__registered_eggs
179175
180    def register(self, egg, name):176    def register(self, egg, name):
181        if not name.isidentifier() or keyword.iskeyword(name):177        if not name.isidentifier() or keyword.iskeyword(name):
182            raise ValueError("Invalid registration name")178            raise ValueError("Invalid registration name")
183        if name in self.__eggs_by_name:179        if name in self.__eggs_by_name:
184            raise ValueError(f"Egg with name {name} has already been registered")180            raise ValueError(f"Egg with name {name} has already been registered")
185        if egg in self.__registered_eggs:181        if egg in self.__registered_eggs:
186            raise ValueError("An egg cannot be registered in multiple tournaments")182            raise ValueError("An egg cannot be registered in multiple tournaments")
187183
188        egg.set_observer(self._record_battle)184        egg.set_observer(self._record_battle)
189        self.__eggs_by_name[name] = egg185        self.__eggs_by_name[name] = egg
190        self.__registered_eggs.add(egg)186        self.__registered_eggs.add(egg)
191        self.__wins[egg] = 0187        self.__wins[egg] = 0
192188
193    def _record_battle(self, e1, e2, side, winner):189    def _record_battle(self, e1, e2, side, winner):
194        if e1 not in self.__registered_eggs or e2 not in self.__registered_eggs:190        if e1 not in self.__registered_eggs or e2 not in self.__registered_eggs:
195            return191            return
196192
197        key = (frozenset({e1, e2}), side)193        key = (frozenset({e1, e2}), side)
198        if key in self.__battles:194        if key in self.__battles:
199            self.__wins[self.__battles[key]] -= 1195            self.__wins[self.__battles[key]] -= 1
200196
201        self.__battles[key] = winner197        self.__battles[key] = winner
202        self.__wins[winner] += 1198        self.__wins[winner] += 1
203        self.__ranking_cache = None199        self.__ranking_cache = None
204200
205    def _sorted_unique_wins(self):201    def _sorted_unique_wins(self):
206        if self.__ranking_cache is None:202        if self.__ranking_cache is None:
207            self.__ranking_cache = sorted(set(self.__wins.values()), reverse=True)203            self.__ranking_cache = sorted(set(self.__wins.values()), reverse=True)
208        return self.__ranking_cache204        return self.__ranking_cache
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op