Домашни > Работилница за отвари! > Решения > Решението на Габриела Костева

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

7 точки общо

11 успешни теста
9 неуспешни теста
Код

  1import math
  2
  3
  4class Potion:
  5    def __init__(self, effects, duration, intensities=None):
  6        self.effects = effects
  7        self.intensities = intensities or {effect: 1 for effect in effects}
  8        self.duration = duration
  9        self.remaining_duration = duration
 10        self.initial_state = None
 11        self.applied_effects = set()
 12        self.used = False
 13
 14        for effect_name in effects:
 15            setattr(self, effect_name, lambda target, name=effect_name: self.apply_effect(name, target))
 16
 17    def apply_effect(self, effect, target, intensity=1):
 18        if self.used:
 19            raise TypeError("Potion is now part of something bigger than itself.")
 20
 21        if effect in self.applied_effects:
 22            raise TypeError("Effect is depleted.")
 23
 24        if effect in self.effects:
 25            if effect in self.intensities:
 26                intensity = self.intensities[effect]
 27
 28            if self.initial_state is None:
 29                self.initial_state = self.save_state(target)
 30
 31            for _ in range(intensity):
 32                self.effects[effect](target)
 33            self.applied_effects.add(effect)
 34        else:
 35            raise ValueError("Invalid effect name.")
 36
 37    def apply_all_effects(self, target):
 38        for effect in self.effects:
 39            self.apply_effect(effect, target)
 40
 41    def save_state(self, target):
 42        return vars(target).copy()
 43
 44    def revert_state(self, target):
 45        if self.initial_state is not None:
 46            for attr, value in self.initial_state.items():
 47                setattr(target, attr, value)
 48
 49    def round_intensity(self, intensity, power):
 50        if not isinstance(power, int) or power < 1:
 51            return math.floor(intensity * power) if intensity <= power else math.ceil(intensity * power)
 52        else:
 53            return intensity * power
 54
 55    def __add__(self, other):
 56        combined_effects = {}
 57        combined_intensities = {}
 58
 59        for effect, intensity in self.intensities.items():
 60            combined_effects[effect] = self.effects[effect]
 61            combined_intensities[effect] = intensity
 62
 63        for effect, intensity in other.intensities.items():
 64            if effect in combined_effects:
 65                combined_intensities[effect] += intensity
 66            else:
 67                combined_effects[effect] = other.effects[effect]
 68                combined_intensities[effect] = intensity
 69        combined_duration = max(self.duration, other.duration)
 70        self.used = True
 71        return Potion(combined_effects, combined_duration, intensities=combined_intensities)
 72
 73    def __mul__(self, power):
 74        updated_effects = {}
 75        updated_intensities = {}
 76
 77        for effect, intensity in self.intensities.items():
 78            updated_effects[effect] = self.effects[effect]
 79            updated_intensities[effect] = self.round_intensity(intensity, power)
 80
 81        self.used = True
 82        return Potion(updated_effects, self.duration * power, intensities=updated_intensities)
 83
 84    def __sub__(self, other):
 85        if not isinstance(other, Potion):
 86            raise TypeError("Subtraction can only be performed with another Potion.")
 87        if not all(effect in self.effects for effect in other.effects):
 88            raise TypeError("Cannot subtract. 'self' does not contain all effects of 'other'.")
 89
 90        subtracted_effects = {}
 91        subtracted_intensities = {}
 92        subtracted_duration = self.duration
 93
 94        for effect, intensity in self.intensities.items():
 95            other_intensity = other.intensities.get(effect, 0)
 96            new_intensity = intensity - other_intensity
 97            if new_intensity >= 0:
 98
 99                subtracted_effects[effect] = self.effects[effect]
100                subtracted_intensities[effect] = new_intensity
101            elif new_intensity < 0:
102                raise TypeError("Negative intensity not allowed during subtraction.")
103
104        self.used = True
105        return Potion(subtracted_effects, subtracted_duration, intensities=subtracted_intensities)
106
107    def __truediv__(self, divisor):
108        if not isinstance(divisor, int) or divisor < 1:
109            raise ValueError("Divisor must be a natural number greater than zero.")
110
111        part_duration = self.duration / divisor
112        part_intensities = {name: round(intensity / divisor) if intensity > 0 else 0 for name, intensity in
113                            self.intensities.items()}
114
115        parts = tuple(Potion(self.effects, part_duration, intensities=part_intensities) for _ in range(divisor))
116        self.used = True
117        return parts
118
119    def tick(self, target):
120        if self.duration > 0:
121            self.duration -= 1
122            if self.duration == 0:
123                self.used = True
124                if self.initial_state is not None:
125                    self.revert_state(target)
126
127    def __eq__(self, other):
128        return self.effects == other.effects and self.intensities == other.intensities
129
130    def __lt__(self, other):
131        return sum(self.intensities.values()) < sum(other.intensities.values())
132
133    def __gt__(self, other):
134        return sum(self.intensities.values()) > sum(other.intensities.values())
135
136    def __hash__(self):
137        return hash(tuple(sorted(self.effects.items())))
138
139    def __getattr__(self, attr):
140        if attr.startswith('make_') and attr[5:] in self.effects:
141            effect_name = attr[5:]
142
143            def repeated_effect(target, intensity=self.effects[effect_name]):
144                for _ in range(intensity):
145                    self.apply_effect(effect_name, target)
146
147            return repeated_effect
148
149        raise AttributeError(f"'Potion' object has no attribute '{attr}'")
150
151    def __call__(self, target):
152        self.apply_all_effects(target)
153
154
155class ГоспожатаПоХимия:
156    def __init__(self):
157        self.used_potions = set()
158        self.target = None
159
160    def apply(self, target, potion):
161        if potion in self.used_potions:
162            raise TypeError("Potion is depleted.")
163
164        if self.target is None:
165            self.target = target
166
167        effects_sorted = sorted(potion.intensities.keys(), key=lambda effect: sum(ord(char) for char in effect),
168                                reverse=True)
169
170        for effect in effects_sorted:
171            if effect not in potion.applied_effects:
172                for _ in range(potion.intensities[effect]):
173                    potion.apply_effect(effect, target)
174                potion.applied_effects.add(effect)
175
176        potion.used = True
177        self.used_potions.add(potion)
178
179    def tick(self):
180        potions_to_remove = set()
181
182        for potion in self.used_potions.copy():
183            potion.tick(self.target)
184
185            if potion.duration == 0:
186                potions_to_remove.add(potion)
187
188        for potion in potions_to_remove:
189            self.used_potions.remove(potion)

.......FF.FFF...EEEF
======================================================================
ERROR: test_ticking_immutable (test.TestГоспожатаПоХимия)
Test ticking after applying a potion with immutable attributes.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 424, in test_ticking_immutable
self._dimitrichka.apply(self._target, potion)
File "/tmp/solution.py", line 173, in apply
potion.apply_effect(effect, target)
File "/tmp/solution.py", line 22, in apply_effect
raise TypeError("Effect is depleted.")
TypeError: Effect is depleted.

======================================================================
ERROR: test_ticking_multiple_potions (test.TestГоспожатаПоХимия)
Test ticking after applying multiple potions which affect the same attribute.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 455, in test_ticking_multiple_potions
self._dimitrichka.apply(self._target, potion2)
File "/tmp/solution.py", line 162, in apply
raise TypeError("Potion is depleted.")
TypeError: Potion is depleted.

======================================================================
ERROR: test_ticking_multiple_targets (test.TestГоспожатаПоХимия)
Test ticking after applying a potion with mutable attributes.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 485, in test_ticking_multiple_targets
self._dimitrichka.apply(target1, potion1)
File "/tmp/solution.py", line 173, in apply
potion.apply_effect(effect, target)
File "/tmp/solution.py", line 22, in apply_effect
raise TypeError("Effect is depleted.")
TypeError: Effect is depleted.

======================================================================
FAIL: test_deprecation (test.TestPotionOperations)
Test deprecation of a potion.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 259, in test_deprecation
with self.assertRaisesRegex(TypeError, 'Potion is now part of something bigger than itself\.'):
AssertionError: TypeError not raised

======================================================================
FAIL: test_dilution (test.TestPotionOperations)
Test dilution of a potion.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 120, in test_dilution
self.assertEqual(self._target.int_attr, 5)
AssertionError: 50 != 5

======================================================================
FAIL: test_purification (test.TestPotionOperations)
Test purification of a potion.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 168, in test_purification
with self.assertRaises(AttributeError):
AssertionError: AttributeError not raised

======================================================================
FAIL: test_separation (test.TestPotionOperations)
Test separation of a potion.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 245, in test_separation
self.assertEqual(self._target.int_attr, 50)
AssertionError: 500 != 50

======================================================================
FAIL: test_applying_depleted_potion (test.TestГоспожатаПоХимия)
Test applying a depleted potion or a potion that was used in a reaction.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 382, in test_applying_depleted_potion
with self.assertRaisesRegex(TypeError, 'Potion is depleted\.'):
AssertionError: TypeError not raised

======================================================================
FAIL: test_ticking_mutable (test.TestГоспожатаПоХимия)
Test ticking after applying a potion with mutable attributes.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 446, in test_ticking_mutable
self.assertEqual(self._target.list_attr, [1, 2, 3])
AssertionError: Lists differ: [1, 2, 3, 4] != [1, 2, 3]

First list contains 1 additional elements.
First extra element 3:
4

- [1, 2, 3, 4]
? ---

+ [1, 2, 3]

----------------------------------------------------------------------
Ran 20 tests in 0.002s

FAILED (failures=6, errors=3)

Дискусия
Георги Кунчев
05.12.2023 11:39

Може да работи и по повече таргети. "дали би било правилно да пазя референция към target-a в класа ГоспожатаПоХимия" - би било, но имай предвид, че може да имаш повече от една госпожа, така че пази данните не в класа, а в инстанциите на класа. Е, ние, няма да тестваме с повече госпожи, но това би бил правилният вариант.
Габриела Костева
05.12.2023 11:23

Имам въпрос по класа ГоспожатаПоХимия. Една инстанция на този клас само върху един target ли може да прилага отвари? Защото към момента не ми работи правилно метода tick, тъй като не мога да върна target-a в началното му състояние, защото нямам референция към него. И се чудех дали би било правилно да пазя референция към target-a в класа ГоспожатаПоХимия
Георги Кунчев
05.12.2023 09:29

Чисто и ясно решение. Добро качество на кода.
История

f1import mathf1import math
22
33
4class Potion:4class Potion:
5    def __init__(self, effects, duration, intensities=None):5    def __init__(self, effects, duration, intensities=None):
6        self.effects = effects6        self.effects = effects
7        self.intensities = intensities or {effect: 1 for effect in effects}7        self.intensities = intensities or {effect: 1 for effect in effects}
8        self.duration = duration8        self.duration = duration
9        self.remaining_duration = duration9        self.remaining_duration = duration
10        self.initial_state = None10        self.initial_state = None
11        self.applied_effects = set()11        self.applied_effects = set()
12        self.used = False12        self.used = False
1313
14        for effect_name in effects:14        for effect_name in effects:
15            setattr(self, effect_name, lambda target, name=effect_name: self.apply_effect(name, target))15            setattr(self, effect_name, lambda target, name=effect_name: self.apply_effect(name, target))
1616
17    def apply_effect(self, effect, target, intensity=1):17    def apply_effect(self, effect, target, intensity=1):
18        if self.used:18        if self.used:
19            raise TypeError("Potion is now part of something bigger than itself.")19            raise TypeError("Potion is now part of something bigger than itself.")
2020
21        if effect in self.applied_effects:21        if effect in self.applied_effects:
22            raise TypeError("Effect is depleted.")22            raise TypeError("Effect is depleted.")
2323
24        if effect in self.effects:24        if effect in self.effects:
25            if effect in self.intensities:25            if effect in self.intensities:
26                intensity = self.intensities[effect]26                intensity = self.intensities[effect]
2727
28            if self.initial_state is None:28            if self.initial_state is None:
29                self.initial_state = self.save_state(target)29                self.initial_state = self.save_state(target)
3030
31            for _ in range(intensity):31            for _ in range(intensity):
32                self.effects[effect](target)32                self.effects[effect](target)
33            self.applied_effects.add(effect)33            self.applied_effects.add(effect)
34        else:34        else:
35            raise ValueError("Invalid effect name.")35            raise ValueError("Invalid effect name.")
3636
37    def apply_all_effects(self, target):37    def apply_all_effects(self, target):
38        for effect in self.effects:38        for effect in self.effects:
39            self.apply_effect(effect, target)39            self.apply_effect(effect, target)
4040
41    def save_state(self, target):41    def save_state(self, target):
42        return vars(target).copy()42        return vars(target).copy()
4343
44    def revert_state(self, target):44    def revert_state(self, target):
n45        target = self.initial_staten45        if self.initial_state is not None:
46            for attr, value in self.initial_state.items():
47                setattr(target, attr, value)
48 
49    def round_intensity(self, intensity, power):
50        if not isinstance(power, int) or power < 1:
51            return math.floor(intensity * power) if intensity <= power else math.ceil(intensity * power)
52        else:
53            return intensity * power
4654
47    def __add__(self, other):55    def __add__(self, other):
48        combined_effects = {}56        combined_effects = {}
49        combined_intensities = {}57        combined_intensities = {}
5058
51        for effect, intensity in self.intensities.items():59        for effect, intensity in self.intensities.items():
52            combined_effects[effect] = self.effects[effect]60            combined_effects[effect] = self.effects[effect]
53            combined_intensities[effect] = intensity61            combined_intensities[effect] = intensity
5462
55        for effect, intensity in other.intensities.items():63        for effect, intensity in other.intensities.items():
56            if effect in combined_effects:64            if effect in combined_effects:
57                combined_intensities[effect] += intensity65                combined_intensities[effect] += intensity
58            else:66            else:
59                combined_effects[effect] = other.effects[effect]67                combined_effects[effect] = other.effects[effect]
60                combined_intensities[effect] = intensity68                combined_intensities[effect] = intensity
61        combined_duration = max(self.duration, other.duration)69        combined_duration = max(self.duration, other.duration)
62        self.used = True70        self.used = True
63        return Potion(combined_effects, combined_duration, intensities=combined_intensities)71        return Potion(combined_effects, combined_duration, intensities=combined_intensities)
6472
65    def __mul__(self, power):73    def __mul__(self, power):
66        updated_effects = {}74        updated_effects = {}
67        updated_intensities = {}75        updated_intensities = {}
6876
n69        if not isinstance(power, int) or power < 1:n
70            for effect, intensity in self.intensities.items():77        for effect, intensity in self.intensities.items():
71                diluted_intensity = math.floor(intensity * power) if intensity <= power else math.ceil(
72                    intensity * power)
73                updated_effects[effect] = self.effects[effect]78            updated_effects[effect] = self.effects[effect]
74                updated_intensities[effect] = diluted_intensity if diluted_intensity > 0 else 1
75        else:
76            for effect, intensity in self.intensities.items():
77                updated_effects[effect] = self.effects[effect]
78                updated_intensities[effect] = intensity * power79            updated_intensities[effect] = self.round_intensity(intensity, power)
80 
79        self.used = True81        self.used = True
80        return Potion(updated_effects, self.duration * power, intensities=updated_intensities)82        return Potion(updated_effects, self.duration * power, intensities=updated_intensities)
8183
82    def __sub__(self, other):84    def __sub__(self, other):
83        if not isinstance(other, Potion):85        if not isinstance(other, Potion):
84            raise TypeError("Subtraction can only be performed with another Potion.")86            raise TypeError("Subtraction can only be performed with another Potion.")
85        if not all(effect in self.effects for effect in other.effects):87        if not all(effect in self.effects for effect in other.effects):
86            raise TypeError("Cannot subtract. 'self' does not contain all effects of 'other'.")88            raise TypeError("Cannot subtract. 'self' does not contain all effects of 'other'.")
8789
88        subtracted_effects = {}90        subtracted_effects = {}
89        subtracted_intensities = {}91        subtracted_intensities = {}
90        subtracted_duration = self.duration92        subtracted_duration = self.duration
9193
92        for effect, intensity in self.intensities.items():94        for effect, intensity in self.intensities.items():
93            other_intensity = other.intensities.get(effect, 0)95            other_intensity = other.intensities.get(effect, 0)
94            new_intensity = intensity - other_intensity96            new_intensity = intensity - other_intensity
n95            if new_intensity > 0:n97            if new_intensity >= 0:
98 
96                subtracted_effects[effect] = self.effects[effect]99                subtracted_effects[effect] = self.effects[effect]
97                subtracted_intensities[effect] = new_intensity100                subtracted_intensities[effect] = new_intensity
98            elif new_intensity < 0:101            elif new_intensity < 0:
99                raise TypeError("Negative intensity not allowed during subtraction.")102                raise TypeError("Negative intensity not allowed during subtraction.")
100103
101        self.used = True104        self.used = True
102        return Potion(subtracted_effects, subtracted_duration, intensities=subtracted_intensities)105        return Potion(subtracted_effects, subtracted_duration, intensities=subtracted_intensities)
103106
104    def __truediv__(self, divisor):107    def __truediv__(self, divisor):
105        if not isinstance(divisor, int) or divisor < 1:108        if not isinstance(divisor, int) or divisor < 1:
106            raise ValueError("Divisor must be a natural number greater than zero.")109            raise ValueError("Divisor must be a natural number greater than zero.")
107110
108        part_duration = self.duration / divisor111        part_duration = self.duration / divisor
109        part_intensities = {name: round(intensity / divisor) if intensity > 0 else 0 for name, intensity in112        part_intensities = {name: round(intensity / divisor) if intensity > 0 else 0 for name, intensity in
110                            self.intensities.items()}113                            self.intensities.items()}
111114
112        parts = tuple(Potion(self.effects, part_duration, intensities=part_intensities) for _ in range(divisor))115        parts = tuple(Potion(self.effects, part_duration, intensities=part_intensities) for _ in range(divisor))
113        self.used = True116        self.used = True
114        return parts117        return parts
115118
n116    def tick(self):n119    def tick(self, target):
117        if not self.used and self.duration > 0:120        if self.duration > 0:
118            self.duration -= 1121            self.duration -= 1
119            if self.duration == 0:122            if self.duration == 0:
120                self.used = True123                self.used = True
n121                if self.initial_state is not None and self.target is not None:n124                if self.initial_state is not None:
122                    self.revert_state(self.target)125                    self.revert_state(target)
123126
124    def __eq__(self, other):127    def __eq__(self, other):
125        return self.effects == other.effects and self.intensities == other.intensities128        return self.effects == other.effects and self.intensities == other.intensities
126129
127    def __lt__(self, other):130    def __lt__(self, other):
128        return sum(self.intensities.values()) < sum(other.intensities.values())131        return sum(self.intensities.values()) < sum(other.intensities.values())
129132
130    def __gt__(self, other):133    def __gt__(self, other):
131        return sum(self.intensities.values()) > sum(other.intensities.values())134        return sum(self.intensities.values()) > sum(other.intensities.values())
132135
133    def __hash__(self):136    def __hash__(self):
134        return hash(tuple(sorted(self.effects.items())))137        return hash(tuple(sorted(self.effects.items())))
135138
136    def __getattr__(self, attr):139    def __getattr__(self, attr):
137        if attr.startswith('make_') and attr[5:] in self.effects:140        if attr.startswith('make_') and attr[5:] in self.effects:
138            effect_name = attr[5:]141            effect_name = attr[5:]
139142
140            def repeated_effect(target, intensity=self.effects[effect_name]):143            def repeated_effect(target, intensity=self.effects[effect_name]):
141                for _ in range(intensity):144                for _ in range(intensity):
142                    self.apply_effect(effect_name, target)145                    self.apply_effect(effect_name, target)
143146
144            return repeated_effect147            return repeated_effect
145148
146        raise AttributeError(f"'Potion' object has no attribute '{attr}'")149        raise AttributeError(f"'Potion' object has no attribute '{attr}'")
147150
148    def __call__(self, target):151    def __call__(self, target):
149        self.apply_all_effects(target)152        self.apply_all_effects(target)
150153
151154
152class ГоспожатаПоХимия:155class ГоспожатаПоХимия:
153    def __init__(self):156    def __init__(self):
154        self.used_potions = set()157        self.used_potions = set()
nn158        self.target = None
155159
156    def apply(self, target, potion):160    def apply(self, target, potion):
157        if potion in self.used_potions:161        if potion in self.used_potions:
158            raise TypeError("Potion is depleted.")162            raise TypeError("Potion is depleted.")
nn163 
164        if self.target is None:
165            self.target = target
159166
160        effects_sorted = sorted(potion.intensities.keys(), key=lambda effect: sum(ord(char) for char in effect),167        effects_sorted = sorted(potion.intensities.keys(), key=lambda effect: sum(ord(char) for char in effect),
161                                reverse=True)168                                reverse=True)
162169
163        for effect in effects_sorted:170        for effect in effects_sorted:
164            if effect not in potion.applied_effects:171            if effect not in potion.applied_effects:
165                for _ in range(potion.intensities[effect]):172                for _ in range(potion.intensities[effect]):
166                    potion.apply_effect(effect, target)173                    potion.apply_effect(effect, target)
167                potion.applied_effects.add(effect)174                potion.applied_effects.add(effect)
168175
169        potion.used = True176        potion.used = True
170        self.used_potions.add(potion)177        self.used_potions.add(potion)
171178
172    def tick(self):179    def tick(self):
nn180        potions_to_remove = set()
181 
173        for potion in self.used_potions.copy():182        for potion in self.used_potions.copy():
n174            potion.tick()n183            potion.tick(self.target)
175184
n176        for potion in self.used_potions:n
177            if potion.duration == 0:185            if potion.duration == 0:
tt186                potions_to_remove.add(potion)
187 
188        for potion in potions_to_remove:
178                self.used_potions.remove(potion)189            self.used_potions.remove(potion)
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op