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

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

9 точки общо

16 успешни теста
4 неуспешни теста
Код (Tick function added)

  1class Potion:
  2    
  3    def __init__(self, effects, duration):
  4        self.effects = effects
  5        self.duration = duration
  6        self.applied_effects = set()
  7        self.intensity = {effect_name: 1 for effect_name in self.effects.keys()}
  8        self.used = False
  9
 10    def apply_effect(self, effect, target):
 11        if self.used:
 12            raise TypeError("Potion is now part of something bigger than itself.")
 13        
 14        if effect in self.applied_effects:
 15            raise TypeError("Effect is depleted.")
 16        
 17        self.applied_effects.add(effect)
 18        for _ in range(self.intensity[effect]):
 19            self.effects[effect](target)
 20        self.intensity[effect] = 0
 21
 22    def __getattr__(self, effect):
 23        if self.used:
 24            raise TypeError("Potion is now part of something bigger than itself.")
 25        
 26        if effect not in self.effects:
 27            raise AttributeError(f"'Potion' object has no attribute '{effect}'")
 28        
 29        return lambda target: self.apply_effect(effect, target)
 30    
 31    def __add__(self, other_potion):
 32        if self.used or other_potion.used:
 33            raise TypeError("Potion is now part of something bigger than itself.")
 34        
 35        new_combined_duration = max(self.duration, other_potion.duration)
 36        new_combined_effects = {effect_name: effect_method for effect_name, effect_method in self.effects.items() if effect_name not in self.applied_effects}
 37        new_combined_intensity = {effect_name: effect_intensity for effect_name, effect_intensity in self.intensity.items() if effect_name not in self.applied_effects}
 38
 39        for effect_name in other_potion.effects:
 40            if effect_name in other_potion.applied_effects:
 41                continue
 42            if effect_name in new_combined_effects.keys():
 43                new_combined_intensity[effect_name] += other_potion.intensity[effect_name]
 44            else:
 45                new_combined_intensity[effect_name] = other_potion.intensity[effect_name]
 46                new_combined_effects[effect_name] = other_potion.effects[effect_name]
 47
 48        self.used = True
 49        other_potion.used = True
 50        new_potion = Potion(new_combined_effects, new_combined_duration)
 51        new_potion.intensity = new_combined_intensity
 52        return new_potion
 53    
 54    def round_intensity_by_remainder(self, current_intensity):
 55        """Helper function to round down intensity when the result is <= x.5 and round up when > x.5"""
 56
 57        for effect_name, result in current_intensity.items():
 58            whole_part_of_result = int(result)
 59            diff_results = result - whole_part_of_result
 60            if diff_results <= 0.5:
 61                current_intensity[effect_name] = whole_part_of_result
 62            else:
 63                current_intensity[effect_name] = whole_part_of_result + 1
 64
 65    def __mul__(self, number):
 66        if self.used:
 67            raise TypeError("Potion is now part of something bigger than itself.")
 68        
 69        new_combined_effects = {effect_name: effect_method for effect_name, effect_method in self.effects.items() if effect_name not in self.applied_effects}
 70        new_multiple_intensity = {effect_name: self.intensity[effect_name] * number for effect_name in self.intensity.keys() if effect_name not in self.applied_effects}
 71        new_multiple_duration = self.duration
 72
 73        self.round_intensity_by_remainder(new_multiple_intensity)
 74
 75        self.used = True
 76        new_potion = Potion(new_combined_effects, new_multiple_duration)
 77        new_potion.intensity = new_multiple_intensity
 78        return new_potion
 79    
 80    def __sub__(self, other_potion):
 81        if self.used or other_potion.used:
 82            raise TypeError("Potion is now part of something bigger than itself.")
 83        
 84        if not all(effect in self.effects for effect in other_potion.effects):
 85            raise TypeError("Right potion effects are not in left potion effects ;()")
 86        
 87        new_sub_effects = {effect_name: effect_method for effect_name, effect_method in self.effects.items() if effect_name not in self.applied_effects}
 88        new_sub_intensity = {effect_name: effect_intensity for effect_name, effect_intensity in self.intensity.items() if effect_name not in self.applied_effects}
 89        new_sub_duration = self.duration
 90
 91        for effect_name in self.intensity:
 92            if effect_name in self.applied_effects:
 93                continue
 94            if effect_name in other_potion.effects and effect_name not in other_potion.applied_effects:
 95                if new_sub_intensity[effect_name] - other_potion.intensity[effect_name] <= 0:
 96                    del new_sub_effects[effect_name]
 97                    del new_sub_intensity[effect_name]
 98                else:
 99                    new_sub_intensity[effect_name] -= other_potion.intensity[effect_name]
100
101        self.used = True
102        other_potion.used = True
103        new_potion = Potion(new_sub_effects, new_sub_duration)
104        new_potion.intensity = new_sub_intensity
105        return new_potion
106    
107    def __truediv__(self, number):
108        if self.used:
109            raise TypeError("Potion is now part of something bigger than itself.")
110        
111        new_div_effects = {effect_name: effect_method for effect_name, effect_method in self.effects.items() if effect_name not in self.applied_effects}
112        new_div_intensity = {effect_name: self.intensity[effect_name] / number for effect_name in self.intensity.keys() if effect_name not in self.applied_effects}
113        new_div_duration = self.duration
114
115        self.round_intensity_by_remainder(new_div_intensity)
116
117        self.used = True
118        result_potions = []
119        for _ in range(number):
120            new_potion = Potion(new_div_effects, new_div_duration)
121            new_potion.intensity = dict(new_div_intensity)
122            result_potions.append(new_potion)
123
124        return result_potions
125    
126    def __hash__(self):
127        return hash(tuple(sorted(self.effects.items())))
128
129    def __eq__(self, other_potion):
130        if self.used or other_potion.used:
131            raise TypeError("Potion is now part of something bigger than itself.")
132        
133        return self.effects == other_potion.effects and self.intensity == other_potion.intensity
134    
135    def __lt__(self, other_potion):
136        if self.used or other_potion.used:
137            raise TypeError("Potion is now part of something bigger than itself.")
138        
139        return sum(self.intensity.values()) < sum(other_potion.intensity.values())
140    
141    
142class ГоспожатаПоХимия:
143    
144    def __init__(self):
145        self.applied_potions = set()
146        self.targets_variables = {}
147        self.target_potions = {}
148
149    def apply(self, target, potion):
150        if potion in self.applied_potions:
151            raise TypeError("Potion is depleted.")
152
153        if potion.used:
154            raise TypeError("Potion is now part of something bigger than itself.")
155
156        if target not in self.targets_variables.keys():
157            self.targets_variables[target] = dict(vars(target))
158            self.target_potions[target] = set()
159
160        self.applied_potions.add(potion)
161
162        self.target_potions[target].add(potion)
163        effects_sorted = sorted(potion.effects.keys(), key=lambda effect_name: sum(map(ord, effect_name)), reverse=True)
164
165        for effect in effects_sorted:
166            if effect not in potion.applied_effects and potion.duration != 0:
167                potion.apply_effect(effect, target)
168
169        potion.used = True
170
171    def reset_target(self, target):
172        for variable in self.targets_variables[target]:
173            target.__setattr__(variable, self.targets_variables[target][variable])
174
175    def tick(self):
176        """A function that reduces the duration of potions.
177        As soon as the duration of a potion becomes 0, it is removed and the target state reset to its original, 
178        but in order not to lose the magic of the ramaining potions, we apply them again.(Because we have hidden some in our pocket. ;) )"""
179
180        is_poped_potion = False
181        for target, potions in self.target_potions.items():
182            for potion in potions:
183                potion.duration -= 1
184                if potion.duration == 0:
185                    self.reset_target(target)
186                    self.target_potions[target].remove(potion)
187                    is_poped_potion = True
188                if is_poped_potion:
189                    for potion in potions:
190                        potion.duration -= 1
191                        for effect in potion.effects:
192                            potion.effects[effect](target)
193                    break

............F....EEF
======================================================================
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 151, in apply
if potion in self.applied_potions:
File "/tmp/solution.py", line 132, in __eq__
raise TypeError("Potion is now part of something bigger than itself.")
TypeError: Potion is now part of something bigger than itself.

======================================================================
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 486, in test_ticking_multiple_targets
self._dimitrichka.apply(target2, potion2)
File "/tmp/solution.py", line 151, in apply
if potion in self.applied_potions:
File "/tmp/solution.py", line 132, in __eq__
raise TypeError("Potion is now part of something bigger than itself.")
TypeError: Potion is now part of something bigger than itself.

======================================================================
FAIL: test_applying_depleted_potion (test.TestГоспожатаПоХимия)
Test applying a depleted potion or a potion that was used in a reaction.
----------------------------------------------------------------------
TypeError: Potion is now part of something bigger than itself.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/tmp/test.py", line 382, in test_applying_depleted_potion
with self.assertRaisesRegex(TypeError, 'Potion is depleted\.'):
AssertionError: "Potion is depleted\." does not match "Potion is now part of something bigger than itself."

======================================================================
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=2, errors=2)

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

Като стил не мога да кажа нищо. Чудесно написано. Единствените коментари, които бих оставил, са по-скоро субективни, така че ще си замълча. Доста чист и подреден код.
История

f1f1
2class Potion:2class Potion:
3    3    
4    def __init__(self, effects, duration):4    def __init__(self, effects, duration):
5        self.effects = effects5        self.effects = effects
6        self.duration = duration6        self.duration = duration
7        self.applied_effects = set()7        self.applied_effects = set()
8        self.intensity = {effect_name: 1 for effect_name in self.effects.keys()}8        self.intensity = {effect_name: 1 for effect_name in self.effects.keys()}
9        self.used = False9        self.used = False
1010
11    def apply_effect(self, effect, target):11    def apply_effect(self, effect, target):
12        if self.used:12        if self.used:
13            raise TypeError("Potion is now part of something bigger than itself.")13            raise TypeError("Potion is now part of something bigger than itself.")
14        14        
15        if effect in self.applied_effects:15        if effect in self.applied_effects:
16            raise TypeError("Effect is depleted.")16            raise TypeError("Effect is depleted.")
17        17        
18        self.applied_effects.add(effect)18        self.applied_effects.add(effect)
19        for _ in range(self.intensity[effect]):19        for _ in range(self.intensity[effect]):
20            self.effects[effect](target)20            self.effects[effect](target)
nn21        self.intensity[effect] = 0
2122
22    def __getattr__(self, effect):23    def __getattr__(self, effect):
nn24        if self.used:
25            raise TypeError("Potion is now part of something bigger than itself.")
26        
23        if effect not in self.effects:27        if effect not in self.effects:
24            raise AttributeError(f"'Potion' object has no attribute '{effect}'")28            raise AttributeError(f"'Potion' object has no attribute '{effect}'")
25        29        
26        return lambda target: self.apply_effect(effect, target)30        return lambda target: self.apply_effect(effect, target)
27    31    
28    def __add__(self, other_potion):32    def __add__(self, other_potion):
n29        if self.used:n33        if self.used or other_potion.used:
30            raise TypeError("Potion is now part of something bigger than itself.")34            raise TypeError("Potion is now part of something bigger than itself.")
31        35        
32        new_combined_duration = max(self.duration, other_potion.duration)36        new_combined_duration = max(self.duration, other_potion.duration)
n33        new_combined_effects = {effect_name: effect_method for effect_name, effect_method in self.effects.items()}n37        new_combined_effects = {effect_name: effect_method for effect_name, effect_method in self.effects.items() if effect_name not in self.applied_effects}
34        new_combined_intensity = {effect_name: effect_intensity for effect_name, effect_intensity in self.intensity.items()}38        new_combined_intensity = {effect_name: effect_intensity for effect_name, effect_intensity in self.intensity.items() if effect_name not in self.applied_effects}
3539
36        for effect_name in other_potion.effects:40        for effect_name in other_potion.effects:
nn41            if effect_name in other_potion.applied_effects:
42                continue
37            if effect_name in new_combined_effects.keys():43            if effect_name in new_combined_effects.keys():
38                new_combined_intensity[effect_name] += other_potion.intensity[effect_name]44                new_combined_intensity[effect_name] += other_potion.intensity[effect_name]
39            else:45            else:
n40                new_combined_intensity[effect_name] = 1n46                new_combined_intensity[effect_name] = other_potion.intensity[effect_name]
41                new_combined_effects[effect_name] = other_potion.effects[effect_name]47                new_combined_effects[effect_name] = other_potion.effects[effect_name]
4248
43        self.used = True49        self.used = True
nn50        other_potion.used = True
44        new_potion = Potion(new_combined_effects, new_combined_duration)51        new_potion = Potion(new_combined_effects, new_combined_duration)
45        new_potion.intensity = new_combined_intensity52        new_potion.intensity = new_combined_intensity
46        return new_potion53        return new_potion
47    54    
48    def round_intensity_by_remainder(self, current_intensity):55    def round_intensity_by_remainder(self, current_intensity):
49        """Helper function to round down intensity when the result is <= x.5 and round up when > x.5"""56        """Helper function to round down intensity when the result is <= x.5 and round up when > x.5"""
5057
51        for effect_name, result in current_intensity.items():58        for effect_name, result in current_intensity.items():
52            whole_part_of_result = int(result)59            whole_part_of_result = int(result)
53            diff_results = result - whole_part_of_result60            diff_results = result - whole_part_of_result
54            if diff_results <= 0.5:61            if diff_results <= 0.5:
55                current_intensity[effect_name] = whole_part_of_result62                current_intensity[effect_name] = whole_part_of_result
56            else:63            else:
57                current_intensity[effect_name] = whole_part_of_result + 164                current_intensity[effect_name] = whole_part_of_result + 1
5865
59    def __mul__(self, number):66    def __mul__(self, number):
60        if self.used:67        if self.used:
61            raise TypeError("Potion is now part of something bigger than itself.")68            raise TypeError("Potion is now part of something bigger than itself.")
62        69        
n63        new_combined_effects = {effect_name: effect_method for effect_name, effect_method in self.effects.items()}n70        new_combined_effects = {effect_name: effect_method for effect_name, effect_method in self.effects.items() if effect_name not in self.applied_effects}
64        new_multiple_intensity = {effect_name: self.intensity[effect_name] * number for effect_name in self.intensity.keys()}71        new_multiple_intensity = {effect_name: self.intensity[effect_name] * number for effect_name in self.intensity.keys() if effect_name not in self.applied_effects}
65        new_multiple_duration = self.duration72        new_multiple_duration = self.duration
6673
67        self.round_intensity_by_remainder(new_multiple_intensity)74        self.round_intensity_by_remainder(new_multiple_intensity)
6875
69        self.used = True76        self.used = True
70        new_potion = Potion(new_combined_effects, new_multiple_duration)77        new_potion = Potion(new_combined_effects, new_multiple_duration)
71        new_potion.intensity = new_multiple_intensity78        new_potion.intensity = new_multiple_intensity
72        return new_potion79        return new_potion
73    80    
74    def __sub__(self, other_potion):81    def __sub__(self, other_potion):
n75        if self.used:n82        if self.used or other_potion.used:
76            raise TypeError("Potion is now part of something bigger than itself.")83            raise TypeError("Potion is now part of something bigger than itself.")
77        84        
78        if not all(effect in self.effects for effect in other_potion.effects):85        if not all(effect in self.effects for effect in other_potion.effects):
79            raise TypeError("Right potion effects are not in left potion effects ;()")86            raise TypeError("Right potion effects are not in left potion effects ;()")
80        87        
n81        new_sub_effects = {effect_name: effect_method for effect_name, effect_method in self.effects.items()}n88        new_sub_effects = {effect_name: effect_method for effect_name, effect_method in self.effects.items() if effect_name not in self.applied_effects}
82        new_sub_intensity = {effect_name: effect_intensity for effect_name, effect_intensity in self.intensity.items()}89        new_sub_intensity = {effect_name: effect_intensity for effect_name, effect_intensity in self.intensity.items() if effect_name not in self.applied_effects}
83        new_sub_duration = self.duration90        new_sub_duration = self.duration
8491
85        for effect_name in self.intensity:92        for effect_name in self.intensity:
n86            if effect_name in other_potion.effects:n93            if effect_name in self.applied_effects:
94                continue
95            if effect_name in other_potion.effects and effect_name not in other_potion.applied_effects:
87                if new_sub_intensity[effect_name] - other_potion.intensity[effect_name] <= 0:96                if new_sub_intensity[effect_name] - other_potion.intensity[effect_name] <= 0:
88                    del new_sub_effects[effect_name]97                    del new_sub_effects[effect_name]
89                    del new_sub_intensity[effect_name]98                    del new_sub_intensity[effect_name]
nn99                else:
100                    new_sub_intensity[effect_name] -= other_potion.intensity[effect_name]
90101
91        self.used = True102        self.used = True
nn103        other_potion.used = True
92        new_potion = Potion(new_sub_effects, new_sub_duration)104        new_potion = Potion(new_sub_effects, new_sub_duration)
93        new_potion.intensity = new_sub_intensity105        new_potion.intensity = new_sub_intensity
94        return new_potion106        return new_potion
95    107    
96    def __truediv__(self, number):108    def __truediv__(self, number):
97        if self.used:109        if self.used:
98            raise TypeError("Potion is now part of something bigger than itself.")110            raise TypeError("Potion is now part of something bigger than itself.")
99        111        
n100        new_div_effects = {effect_name: effect_method for effect_name, effect_method in self.effects.items()}n112        new_div_effects = {effect_name: effect_method for effect_name, effect_method in self.effects.items() if effect_name not in self.applied_effects}
101        new_div_intensity = {effect_name: self.intensity[effect_name] / number for effect_name in self.intensity.keys()}113        new_div_intensity = {effect_name: self.intensity[effect_name] / number for effect_name in self.intensity.keys() if effect_name not in self.applied_effects}
102        new_div_duration = self.duration114        new_div_duration = self.duration
103115
104        self.round_intensity_by_remainder(new_div_intensity)116        self.round_intensity_by_remainder(new_div_intensity)
105117
106        self.used = True118        self.used = True
107        result_potions = []119        result_potions = []
108        for _ in range(number):120        for _ in range(number):
109            new_potion = Potion(new_div_effects, new_div_duration)121            new_potion = Potion(new_div_effects, new_div_duration)
n110            new_potion.intensity = new_div_intensityn122            new_potion.intensity = dict(new_div_intensity)
111            result_potions.append(new_potion)123            result_potions.append(new_potion)
112124
113        return result_potions125        return result_potions
114    126    
115    def __hash__(self):127    def __hash__(self):
116        return hash(tuple(sorted(self.effects.items())))128        return hash(tuple(sorted(self.effects.items())))
117129
118    def __eq__(self, other_potion):130    def __eq__(self, other_potion):
nn131        if self.used or other_potion.used:
132            raise TypeError("Potion is now part of something bigger than itself.")
133        
119        return self.effects == other_potion.effects and self.intensity == other_potion.intensity134        return self.effects == other_potion.effects and self.intensity == other_potion.intensity
120    135    
121    def __lt__(self, other_potion):136    def __lt__(self, other_potion):
nn137        if self.used or other_potion.used:
138            raise TypeError("Potion is now part of something bigger than itself.")
139        
122        return sum(self.intensity.values()) < sum(other_potion.intensity.values())140        return sum(self.intensity.values()) < sum(other_potion.intensity.values())
nn141    
123    142    
124class ГоспожатаПоХимия:143class ГоспожатаПоХимия:
125    144    
126    def __init__(self):145    def __init__(self):
n127        self.applied_potions = []n146        self.applied_potions = set()
128        self.targets_variables = {}147        self.targets_variables = {}
129        self.target_potions = {}148        self.target_potions = {}
130149
131    def apply(self, target, potion):150    def apply(self, target, potion):
132        if potion in self.applied_potions:151        if potion in self.applied_potions:
133            raise TypeError("Potion is depleted.")152            raise TypeError("Potion is depleted.")
n134        n153 
135        if potion.used:154        if potion.used:
136            raise TypeError("Potion is now part of something bigger than itself.")155            raise TypeError("Potion is now part of something bigger than itself.")
137156
138        if target not in self.targets_variables.keys():157        if target not in self.targets_variables.keys():
139            self.targets_variables[target] = dict(vars(target))158            self.targets_variables[target] = dict(vars(target))
n140            self.target_potions[target] = []n159            self.target_potions[target] = set()
141160
n142        self.applied_potions.append(potion)n161        self.applied_potions.add(potion)
143162
n144        self.target_potions[target].append(potion)n163        self.target_potions[target].add(potion)
145        self.target_potions[target] = sorted(self.target_potions[target])
146        effects_sorted = sorted(potion.effects.keys(), key=lambda effect_name: sum(map(ord, effect_name)), reverse=True)164        effects_sorted = sorted(potion.effects.keys(), key=lambda effect_name: sum(map(ord, effect_name)), reverse=True)
147165
148        for effect in effects_sorted:166        for effect in effects_sorted:
n149            if effect not in potion.applied_effects:n167            if effect not in potion.applied_effects and potion.duration != 0:
150                potion.apply_effect(effect, target)168                potion.apply_effect(effect, target)
151169
152        potion.used = True170        potion.used = True
153171
154    def reset_target(self, target):172    def reset_target(self, target):
155        for variable in self.targets_variables[target]:173        for variable in self.targets_variables[target]:
156            target.__setattr__(variable, self.targets_variables[target][variable])174            target.__setattr__(variable, self.targets_variables[target][variable])
157175
t158    #In progress...t
159    #def tick(self):176    def tick(self):
177        """A function that reduces the duration of potions.
178        As soon as the duration of a potion becomes 0, it is removed and the target state reset to its original, 
179        but in order not to lose the magic of the ramaining potions, we apply them again.(Because we have hidden some in our pocket. ;) )"""
180 
160        # is_poped_potion = False181        is_poped_potion = False
161        # for target, potions in self.potions_and_target.items():182        for target, potions in self.target_potions.items():
162        #     for potion in potions:183            for potion in potions:
163        #         potion.duration -= 1184                potion.duration -= 1
164        #         if potion.duration <= 0:185                if potion.duration == 0:
165        #             self.reset_target(target)186                    self.reset_target(target)
166        #             self.potions_and_target[target].remove(potion)187                    self.target_potions[target].remove(potion)
167        #             self.applied_potions.clear()
168        #             is_poped_potion = True188                    is_poped_potion = True
169        #         if is_poped_potion:189                if is_poped_potion:
170        #             for potion in potions:190                    for potion in potions:
191                        potion.duration -= 1
171        #                 for effect in potion.effects:192                        for effect in potion.effects:
172        #                     potion.apply_effect(effecttarget)193                            potion.effects[effect](target)
194                    break
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op