1def multiple_calls_wrapper(func, count):
2 def wrapper(*args, **kwargs):
3 for _ in range(count):
4 func(*args, **kwargs)
5 return wrapper
6
7custom_round = lambda x: int(x) + 1 if x % 1 > 0.5 else int(x)
8
9class Potion:
10 def __init__(self, effects, duration):
11 self.effects = effects
12 self.intensities = {effect : 1 for effect in self.effects.keys()}
13 self.depleted_effects = set()
14 self.is_depleted = False
15 self.is_applied = False
16 self.duration = duration
17
18 def __getattr__(self, name):
19 if self.is_depleted:
20 raise TypeError("Potion is now part of something bigger than itself.")
21 elif self.is_applied or name in self.effects and name in self.depleted_effects:
22 raise TypeError("Effect is depleted.")
23 elif name in self.effects and name not in self.depleted_effects:
24 self.depleted_effects.add(name)
25 self.is_applied = self.depleted_effects == set(self.effects)
26 return multiple_calls_wrapper(self.effects[name], self.intensities[name])
27 else:
28 raise AttributeError(f"Couldn't find {name} attribute")
29
30 def __add__(self, other_potion):
31 if self.is_depleted or other_potion.is_depleted:
32 raise TypeError("Potion is now part of something bigger than itself.")
33 if self.is_applied or other_potion.is_applied:
34 raise TypeError("Potion is depleted.")
35 self.is_depleted = True
36 other_potion.is_depleted = True
37
38 new_duration = max(self.duration, other_potion.duration)
39 new_effects = {**self.effects, **other_potion.effects}
40 #union of intensities, where intensities are summed for common effects
41 new_intensities = {key : self.intensities.get(key) for key in set(self.intensities.keys()) - self.depleted_effects}
42 for key in set(other_potion.intensities.keys()) - other_potion.depleted_effects:
43 if key in new_intensities:
44 new_intensities[key] += other_potion.intensities[key]
45 else:
46 new_intensities[key] = other_potion.intensities[key]
47
48 result = Potion(new_effects, new_duration)
49 result.intensities = new_intensities
50 return result
51
52 def __mul__(self, times):
53 if self.is_depleted:
54 raise TypeError("Potion is now part of something bigger than itself.")
55 if self.is_applied:
56 raise TypeError("Potion is depleted.")
57 self.is_depleted = True
58
59 #dict comprehension with custom rounding criteria
60 #(>x.5 is rounded to x + 1; <=x.5 is rounded to x)
61 new_intensities = {key: custom_round(times * val)
62 for (key, val) in self.intensities.items()}
63 result = Potion(self.effects, self.duration)
64 result.intensities = new_intensities
65 result.depleted_effects = self.depleted_effects
66 return result
67
68
69 def __rmul__(self, times):
70 return self.__mul__(times)
71
72 def __imul__(self, times):
73 self = self.__mul__(times)
74 return self
75
76 def __sub__(self, other_potion):
77 if self.is_depleted or other_potion.is_depleted:
78 raise TypeError("Potion is now part of something bigger than itself.")
79 if self.is_applied or other_potion.is_applied:
80 raise TypeError("Potion is depleted.")
81 self.is_depleted = True
82 other_potion.is_depleted = True
83
84 if not (set(other_potion.effects.keys()) <= set(self.effects.keys())):
85 raise TypeError("Invalid subtract")
86
87 new_intensities = self.intensities
88 for effect in other_potion.intensities:
89 new_intensities[effect] -= other_potion.intensities[effect]
90 if new_intensities[effect] < 0:
91 del new_intensities[effect]
92 new_effects = {key : val for (key, val) in self.effects.items() if key in new_intensities}
93 result = Potion(new_effects, self.duration)
94 result.intensities = new_intensities
95 result.depleted_effects = self.depleted_effects & set(new_effects.keys())
96 return result
97
98 def __truediv__(self, times):
99 if self.is_depleted:
100 raise TypeError("Potion is now part of something bigger than itself.")
101 if self.is_applied:
102 raise TypeError("Potion is depleted.")
103 self.is_depleted = True
104
105 new_intensities = {key : custom_round(val / times) for (key, val) in self.intensities.items()}
106 result = Potion(self.effects, self.duration)
107 result.intensities = new_intensities
108 return tuple([result for _ in range(times)])
109
110 def __eq__(self, other_potion):
111 first_effects = set(self.intensities) - set(self.depleted_effects)
112 second_effects = set(other_potion.intensities) - set(other_potion.depleted_effects)
113 if first_effects != second_effects:
114 return False
115 for effect in first_effects:
116 if other_potion.intensities[effect] != self.intensities[effect]:
117 return False
118 return True
119
120 def __gt__(self, other_potion):
121 first_sum = sum([self.intensities[effect] for effect in self.effects if effect not in self.depleted_effects])
122 second_sum = sum([other_potion.intensities[effect] for effect in other_potion.effects
123 if effect not in other_potion.depleted_effects])
124 return first_sum > second_sum
125
126class ГоспожатаПоХимия:
127 def __init__(self):
128 self.history = {}
129 def apply(self, target, potion, save = True):
130 if potion.is_applied:
131 raise TypeError("Potion is depleted.")
132 if potion.is_depleted:
133 raise TypeError("Potion is now part of something bigger than itself.")
134
135 if save:
136 if not id(target) in self.history.keys():
137 self.history[id(target)] = [target, target.__dict__.copy()]
138 self.history[id(target)].extend([potion.__dict__.copy(), potion.duration])
139
140 inorder_effects = [effect for effect in potion.effects.keys() if not effect in potion.depleted_effects]
141 inorder_effects.sort(key = lambda x : sum([ord(ch) for ch in x]), reverse = True)
142 for effect in inorder_effects:
143 potion.__getattr__(effect)(target)
144
145 potion.is_applied = True
146
147 def reapply_potions(self, new_list):
148 cur_obj = new_list[0]
149 cur_obj.__dict__ = new_list[1]
150 for i in range(2, len(new_list), 2):
151 cur_potion = Potion(None, None)
152 cur_potion.__dict__ = new_list[i]
153 self.apply(cur_obj, cur_potion, False)
154
155
156 def tick(self):
157 for key in self.history.keys():
158 reapply = False
159 new_list = []
160
161 for i in range(len(self.history[key])):
162 if not isinstance(self.history[key][i], int):
163 new_list.append(self.history[key][i])
164 continue
165 self.history[key][i] -= 1
166 if self.history[key][i] == 0:
167 reapply = True
168 new_list.pop()
169 continue
170 new_list.append(self.history[key][i])
171
172 self.history[key] = new_list
173 if reapply:
174 self.reapply_potions(new_list)
..........FE.....E.F
======================================================================
ERROR: test_separation (test.TestPotionOperations)
Test separation of a potion.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 218, in test_separation
potion2.int_attr_fun(self._target)
File "/tmp/solution.py", line 22, in __getattr__
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 457, in test_ticking_multiple_potions
self._dimitrichka.tick()
File "/tmp/solution.py", line 174, in tick
self.reapply_potions(new_list)
File "/tmp/solution.py", line 151, in reapply_potions
cur_potion = Potion(None, None)
File "/tmp/solution.py", line 12, in __init__
self.intensities = {effect : 1 for effect in self.effects.keys()}
AttributeError: 'NoneType' object has no attribute 'keys'
======================================================================
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_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)
05.12.2023 09:40
05.12.2023 09:41
05.12.2023 09:42
05.12.2023 09:42
05.12.2023 09:42
05.12.2023 09:43
05.12.2023 09:44
05.12.2023 09:44
05.12.2023 09:45
05.12.2023 09:46