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

Резултати
10 точки от тестове
2 точки от учител

12 точки общо

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

  1import math
  2from contextlib import suppress
  3from collections import deque
  4from copy import deepcopy
  5
  6TRANSCENDED = 'Potion is now part of something bigger than itself.'
  7DEPLETED_P = 'Potion is depleted.'
  8DEPLETED_E = 'Effect is depleted.'
  9
 10
 11def round_half_down(num):
 12    return math.ceil(num - 0.5)
 13
 14
 15def 使ったら消えてしまう(*msgs):
 16    def decorator(cls):
 17        class UseOnce(cls):
 18            def __init__(self, *args, **kwargs):
 19                super().__init__(*args, **kwargs)
 20                for idx in range(len(msgs)):
 21                    setattr(self, f'__{idx}', False)
 22
 23            def __getattribute__(self, item):
 24                for idx in range(len(msgs)):
 25                    if object.__getattribute__(self, f'__{idx}'):
 26                        raise TypeError(msgs[idx])
 27                return super().__getattribute__(item)
 28
 29        return UseOnce
 30
 31    return decorator
 32
 33
 34def 消えたら伝えろ(idx):
 35    def once(method):
 36        def wrapper(self, other):
 37            res = method(self, other)
 38            setattr(self, f'__{idx}', True)
 39            if type(other) is Potion:
 40                setattr(other, f'__{idx}', True)
 41            return res
 42
 43        return wrapper
 44
 45    return once
 46
 47
 48@使ったら消えてしまう(DEPLETED_E)
 49class Effect:
 50    @staticmethod
 51    def mole_mass(effect):
 52        return sum(ord(char) for char in effect)
 53
 54    def __init__(self, effect):
 55        self.effect = effect
 56        self.intensity = 1
 57
 58    @消えたら伝えろ(0)
 59    def __call__(self, target):
 60        while self.intensity > 0:
 61            self.effect(target)
 62            self.intensity -= 1
 63
 64
 65@使ったら消えてしまう(DEPLETED_P, TRANSCENDED)
 66class Potion:
 67    def __init__(self, effects, duration):
 68        self.effects = effects
 69        self.duration = duration
 70        for effect_name, effect in effects.items():
 71            setattr(self, effect_name, Effect(effect))
 72
 73    def __getitem__(self, effect):
 74        return getattr(self, effect)
 75
 76    def __setitem__(self, effect_name, effect):
 77        self.effects[effect_name] = effect
 78        setattr(self, effect_name, Effect(effect))
 79
 80    def __delitem__(self, key):
 81        delattr(self, key)
 82        del self.effects[key]
 83
 84    @消えたら伝えろ(1)
 85    def __add__(self, other):
 86        potion = Potion({}, max(self.duration, other.duration))
 87        potion._copy_unused_effects(self)
 88        potion._copy_unused_effects(other)
 89        for effect in set(self.effects) & set(other.effects):
 90            with suppress(TypeError):
 91                potion[effect].intensity = self[effect].intensity + other[effect].intensity
 92        return potion
 93
 94    @消えたら伝えろ(1)
 95    def __sub__(self, other):
 96        if set(other.effects) - set(self.effects):
 97            raise TypeError
 98        potion = Potion({}, self.duration)
 99        potion._copy_unused_effects(self)
100        for effect in other.effects:
101            with suppress(TypeError):
102                intensity = self[effect].intensity - other[effect].intensity
103                if intensity <= 0:
104                    del potion[effect]
105                else:
106                    potion[effect].intensity = intensity
107        return potion
108
109    @消えたら伝えろ(1)
110    def __mul__(self, other):
111        potion = Potion({}, self.duration)
112        potion._copy_unused_effects(self)
113        for effect in self.effects:
114            with suppress(TypeError):
115                potion[effect].intensity = round_half_down(self[effect].intensity * other)
116        return potion
117
118    @消えたら伝えろ(1)
119    def __truediv__(self, other):
120        potions = []
121        for _ in range(other):
122            potion = Potion({}, round_half_down(self.duration / other))
123            potion._copy_unused_effects(self)
124            for effect in self.effects:
125                with suppress(TypeError):
126                    potion[effect].intensity = round_half_down(self[effect].intensity / other)
127            potions.append(potion)
128        return potions
129
130    def __eq__(self, other):
131        if self.effects.keys() != other.effects.keys():
132            return False
133        for effect in self.effects:
134            intensity_1, intensity_2 = 0, 0
135            with suppress(TypeError):
136                intensity_1 = self[effect].intensity
137            with suppress(TypeError):
138                intensity_2 = other[effect].intensity
139            if intensity_1 != intensity_2:
140                return False
141        return True
142
143    def __gt__(self, other):
144        return self._total_intensities() > other._total_intensities()
145
146    @消えたら伝えろ(0)
147    def __call__(self, target):
148        for effect in sorted(self.effects, reverse=True, key=Effect.mole_mass):
149            with suppress(TypeError):
150                self[effect](target)
151
152    def __neg__(self):
153        potion = Potion({}, self.duration - 1)
154        potion._copy_unused_effects(self)
155        return potion
156
157    def _total_intensities(self):
158        total = 0
159        for effect in self.effects:
160            with suppress(TypeError):
161                total += self[effect].intensity
162        return total
163
164    def _copy_unused_effects(self, other):
165        for effect in other.effects:
166            with suppress(TypeError):
167                self._add_effect(effect, other.effects[effect], other[effect].intensity)
168
169    def _add_effect(self, effect_name, effect, intensity=1):
170        self[effect_name] = effect
171        self[effect_name].intensity = intensity
172
173
174class ГоспожатаПоХимия:
175    def __init__(self):
176        self.addicts = {}
177        self.drugs = {}
178
179    def apply(self, target, potion):
180        if potion.duration:
181            addict_id = self._make_addict(target)
182            self._add_drug(addict_id, -potion)
183            potion(target)
184
185    def tick(self):
186        for addict in list(self.addicts):
187            self._withdraw(addict)
188
189            if not self.drugs[addict]:
190                self._remove(addict)
191                continue
192
193            self._give_drugs(addict)
194
195    def _make_addict(self, target):
196        if id(target) not in self.addicts:
197            self.addicts[id(target)] = target, deepcopy(target.__dict__)
198            self.drugs[id(target)] = deque()
199        return id(target)
200
201    def _add_drug(self, addict_id, potion):
202        if potion.duration > 0:
203            self.drugs[addict_id].append(potion)
204
205    def _withdraw(self, addict_id):
206        self.addicts[addict_id][0].__dict__ = deepcopy(self.addicts[addict_id][1])
207
208    def _give_drugs(self, addict_id):
209        drugs = self.drugs[addict_id]
210        addict = self.addicts[addict_id][0]
211        drugs_to_take = len(drugs)
212
213        while drugs_to_take > 0:
214            drug = drugs.popleft()
215            self._add_drug(addict_id, -drug)
216            drug(addict)
217            drugs_to_take -= 1
218
219    def _remove(self, addict_id):
220        del self.addicts[addict_id]
221        del self.drugs[addict_id]

....................
----------------------------------------------------------------------
Ran 20 tests in 0.004s

OK

Дискусия
Виктор Бечев
04.12.2023 22:50

お前はもう死んでいる ? :grin:
Костадин Русалов
04.12.2023 20:33

Имената на декораторите са най-трудни за измисляне. Бяха и на японски по едно време
Виктор Бечев
04.12.2023 19:40

Отвъд това, не ми прави впечатление нещо стилистично, което да ти дам като съвет. Виж, за дизайна на декораторите *(не казвам да ги променяш сега, а просто feedback)* - малко е пипкава употребата на индексите. На практика трябва да знаеш какви съобщения си подал на кои индекси в първия декоратор за да можеш да използваш с правилния аргумент втория. Тук върши работа, но ако имаше 6-7 различни съобщения щеше да е доста по-неприятно.
Виктор Бечев
04.12.2023 19:26

Не мога да преценя дали имената на декораторите ти ме кефят или ме подлудяват. 😅
Георги Кунчев
04.12.2023 08:53

- да - не - не
Костадин Русалов
04.12.2023 08:44

Възможни ли са следните ситуации: - една димитричка да приложи отвари върху няколко таргета и да ги следи всички; - върху един таргет да се приложат отвари от повече от една димитрички; - да се прилагат странични отвари (директно, не с `.apply`) върху таргет, който се следи от димитричка?
Георги Кунчев
03.12.2023 21:01

Не е напълно стриктно спрямо условието ни, но за мен най-логичното е следното: Използван ефект в отвара вече има интензитет нула. При изваждане би било ок и няма да получиш изключение, но тъй като резултиращият интензитет е отрицателно число, този ефект не присъства в резултата.
Костадин Русалов
03.12.2023 17:51

A за пречистването, ако в първата отвара има `grow` и го използваме, преди да пречистим с втора, в която има друг `grow`, `TypeError` ли се възбужда?
Георги Кунчев
03.12.2023 17:26

На въпрос 1 - да. За втори въпрос - не. Ако ефектите са използвани поотделно, отварата е все още валидна и може да се прилага или комбинира с друга, просто всичките и ефекти са използвани и отварата не прави нищо.
Костадин Русалов
03.12.2023 17:12

Ако имаме отварa с няколко ефекта, например `grow`, `heal`, `immune` (всички с интензитет 1) и използваме `heal` и `immune`, a след това я комбинираме с друга отвара, която има друг `heal` с интензитет 1, комбинираната би трябвало да има само `grow` и `heal` с интензитети 1, нали? А ако бяхме използвали от първата всички ефекти поотделно, тогава няма как да комбинираме, защото ще се възбуди `TypeError('Potion is depleted.')`, така ли?
Георги Кунчев
03.12.2023 10:48

Некоректност на примера е това. След потенцииране не трябва да може да ае сравнява.
Костадин Русалов
03.12.2023 09:08

В примера с оценяването на отвари `grow_potion` се потенцира, което значи, че става неизползвама, но все пак може да се сравнява. Това означава ли, че преходността не важи при сравнение?
Георги Кунчев
02.12.2023 21:40

С бектикове. Там, където е буквата Ч. Повече инфо: [тук](/info/showdown)
Костадин Русалов
02.12.2023 21:37

Също да питам как се форматира код тук? 😅
Георги Кунчев
02.12.2023 21:34

Не съм сигурен какво целиш, но ако направиш декоратор, който е клас, би следвало да можеш да пазиш всичко. Да, това не сме го застъпвали в лекциите, но можеш. Използвай init и call дъндърите на клас и пак изграждаш декоратор.
Костадин Русалов
02.12.2023 21:31

Качвам това решение само за да отворя дискусия. Искам да попитам дали има как като правя декоратор да взема всички атрибути на декорираната функция, но не като копия, а като референции. Прочетох за `@wraps`, но не ми върши работа, защото се получава следното: ``` def decorator(func): @wraps(func) def wrapper(): return func() return wrapper def func(): pass func.attr = 1 original = func func = decorator(func) func.attr = 2 assert func.attr == original.attr # тук фейлва ``` Има ли как да стане, или трябва сам да си напиша декоратор подобен на wraps
История

f1import mathf1import math
2from contextlib import suppress2from contextlib import suppress
3from collections import deque3from collections import deque
4from copy import deepcopy4from copy import deepcopy
55
6TRANSCENDED = 'Potion is now part of something bigger than itself.'6TRANSCENDED = 'Potion is now part of something bigger than itself.'
7DEPLETED_P = 'Potion is depleted.'7DEPLETED_P = 'Potion is depleted.'
8DEPLETED_E = 'Effect is depleted.'8DEPLETED_E = 'Effect is depleted.'
99
1010
11def round_half_down(num):11def round_half_down(num):
12    return math.ceil(num - 0.5)12    return math.ceil(num - 0.5)
1313
1414
15def 使ったら消えてしまう(*msgs):15def 使ったら消えてしまう(*msgs):
16    def decorator(cls):16    def decorator(cls):
17        class UseOnce(cls):17        class UseOnce(cls):
18            def __init__(self, *args, **kwargs):18            def __init__(self, *args, **kwargs):
19                super().__init__(*args, **kwargs)19                super().__init__(*args, **kwargs)
20                for idx in range(len(msgs)):20                for idx in range(len(msgs)):
21                    setattr(self, f'__{idx}', False)21                    setattr(self, f'__{idx}', False)
2222
23            def __getattribute__(self, item):23            def __getattribute__(self, item):
24                for idx in range(len(msgs)):24                for idx in range(len(msgs)):
25                    if object.__getattribute__(self, f'__{idx}'):25                    if object.__getattribute__(self, f'__{idx}'):
26                        raise TypeError(msgs[idx])26                        raise TypeError(msgs[idx])
27                return super().__getattribute__(item)27                return super().__getattribute__(item)
2828
29        return UseOnce29        return UseOnce
3030
31    return decorator31    return decorator
3232
3333
34def 消えたら伝えろ(idx):34def 消えたら伝えろ(idx):
35    def once(method):35    def once(method):
36        def wrapper(self, other):36        def wrapper(self, other):
37            res = method(self, other)37            res = method(self, other)
38            setattr(self, f'__{idx}', True)38            setattr(self, f'__{idx}', True)
39            if type(other) is Potion:39            if type(other) is Potion:
40                setattr(other, f'__{idx}', True)40                setattr(other, f'__{idx}', True)
41            return res41            return res
4242
43        return wrapper43        return wrapper
4444
45    return once45    return once
4646
4747
48@使ったら消えてしまう(DEPLETED_E)48@使ったら消えてしまう(DEPLETED_E)
49class Effect:49class Effect:
50    @staticmethod50    @staticmethod
51    def mole_mass(effect):51    def mole_mass(effect):
52        return sum(ord(char) for char in effect)52        return sum(ord(char) for char in effect)
5353
54    def __init__(self, effect):54    def __init__(self, effect):
55        self.effect = effect55        self.effect = effect
56        self.intensity = 156        self.intensity = 1
5757
58    @消えたら伝えろ(0)58    @消えたら伝えろ(0)
59    def __call__(self, target):59    def __call__(self, target):
60        while self.intensity > 0:60        while self.intensity > 0:
61            self.effect(target)61            self.effect(target)
62            self.intensity -= 162            self.intensity -= 1
6363
6464
65@使ったら消えてしまう(DEPLETED_P, TRANSCENDED)65@使ったら消えてしまう(DEPLETED_P, TRANSCENDED)
66class Potion:66class Potion:
67    def __init__(self, effects, duration):67    def __init__(self, effects, duration):
68        self.effects = effects68        self.effects = effects
69        self.duration = duration69        self.duration = duration
70        for effect_name, effect in effects.items():70        for effect_name, effect in effects.items():
71            setattr(self, effect_name, Effect(effect))71            setattr(self, effect_name, Effect(effect))
7272
73    def __getitem__(self, effect):73    def __getitem__(self, effect):
74        return getattr(self, effect)74        return getattr(self, effect)
7575
76    def __setitem__(self, effect_name, effect):76    def __setitem__(self, effect_name, effect):
77        self.effects[effect_name] = effect77        self.effects[effect_name] = effect
78        setattr(self, effect_name, Effect(effect))78        setattr(self, effect_name, Effect(effect))
7979
80    def __delitem__(self, key):80    def __delitem__(self, key):
81        delattr(self, key)81        delattr(self, key)
82        del self.effects[key]82        del self.effects[key]
8383
84    @消えたら伝えろ(1)84    @消えたら伝えろ(1)
85    def __add__(self, other):85    def __add__(self, other):
86        potion = Potion({}, max(self.duration, other.duration))86        potion = Potion({}, max(self.duration, other.duration))
87        potion._copy_unused_effects(self)87        potion._copy_unused_effects(self)
88        potion._copy_unused_effects(other)88        potion._copy_unused_effects(other)
89        for effect in set(self.effects) & set(other.effects):89        for effect in set(self.effects) & set(other.effects):
90            with suppress(TypeError):90            with suppress(TypeError):
91                potion[effect].intensity = self[effect].intensity + other[effect].intensity91                potion[effect].intensity = self[effect].intensity + other[effect].intensity
92        return potion92        return potion
9393
94    @消えたら伝えろ(1)94    @消えたら伝えろ(1)
95    def __sub__(self, other):95    def __sub__(self, other):
96        if set(other.effects) - set(self.effects):96        if set(other.effects) - set(self.effects):
97            raise TypeError97            raise TypeError
98        potion = Potion({}, self.duration)98        potion = Potion({}, self.duration)
n99        potion._copy_unused_effects(self)  # do we need it?n99        potion._copy_unused_effects(self)
100        for effect in other.effects:100        for effect in other.effects:
101            with suppress(TypeError):101            with suppress(TypeError):
102                intensity = self[effect].intensity - other[effect].intensity102                intensity = self[effect].intensity - other[effect].intensity
103                if intensity <= 0:103                if intensity <= 0:
104                    del potion[effect]104                    del potion[effect]
105                else:105                else:
106                    potion[effect].intensity = intensity106                    potion[effect].intensity = intensity
107        return potion107        return potion
108108
109    @消えたら伝えろ(1)109    @消えたら伝えろ(1)
110    def __mul__(self, other):110    def __mul__(self, other):
111        potion = Potion({}, self.duration)111        potion = Potion({}, self.duration)
112        potion._copy_unused_effects(self)112        potion._copy_unused_effects(self)
113        for effect in self.effects:113        for effect in self.effects:
114            with suppress(TypeError):114            with suppress(TypeError):
115                potion[effect].intensity = round_half_down(self[effect].intensity * other)115                potion[effect].intensity = round_half_down(self[effect].intensity * other)
116        return potion116        return potion
117117
118    @消えたら伝えろ(1)118    @消えたら伝えろ(1)
119    def __truediv__(self, other):119    def __truediv__(self, other):
120        potions = []120        potions = []
121        for _ in range(other):121        for _ in range(other):
122            potion = Potion({}, round_half_down(self.duration / other))122            potion = Potion({}, round_half_down(self.duration / other))
123            potion._copy_unused_effects(self)123            potion._copy_unused_effects(self)
124            for effect in self.effects:124            for effect in self.effects:
125                with suppress(TypeError):125                with suppress(TypeError):
126                    potion[effect].intensity = round_half_down(self[effect].intensity / other)126                    potion[effect].intensity = round_half_down(self[effect].intensity / other)
127            potions.append(potion)127            potions.append(potion)
128        return potions128        return potions
129129
130    def __eq__(self, other):130    def __eq__(self, other):
131        if self.effects.keys() != other.effects.keys():131        if self.effects.keys() != other.effects.keys():
132            return False132            return False
133        for effect in self.effects:133        for effect in self.effects:
134            intensity_1, intensity_2 = 0, 0134            intensity_1, intensity_2 = 0, 0
135            with suppress(TypeError):135            with suppress(TypeError):
136                intensity_1 = self[effect].intensity136                intensity_1 = self[effect].intensity
137            with suppress(TypeError):137            with suppress(TypeError):
138                intensity_2 = other[effect].intensity138                intensity_2 = other[effect].intensity
139            if intensity_1 != intensity_2:139            if intensity_1 != intensity_2:
140                return False140                return False
141        return True141        return True
142142
143    def __gt__(self, other):143    def __gt__(self, other):
144        return self._total_intensities() > other._total_intensities()144        return self._total_intensities() > other._total_intensities()
145145
146    @消えたら伝えろ(0)146    @消えたら伝えろ(0)
147    def __call__(self, target):147    def __call__(self, target):
148        for effect in sorted(self.effects, reverse=True, key=Effect.mole_mass):148        for effect in sorted(self.effects, reverse=True, key=Effect.mole_mass):
149            with suppress(TypeError):149            with suppress(TypeError):
150                self[effect](target)150                self[effect](target)
151151
152    def __neg__(self):152    def __neg__(self):
153        potion = Potion({}, self.duration - 1)153        potion = Potion({}, self.duration - 1)
154        potion._copy_unused_effects(self)154        potion._copy_unused_effects(self)
155        return potion155        return potion
156156
157    def _total_intensities(self):157    def _total_intensities(self):
158        total = 0158        total = 0
159        for effect in self.effects:159        for effect in self.effects:
160            with suppress(TypeError):160            with suppress(TypeError):
161                total += self[effect].intensity161                total += self[effect].intensity
162        return total162        return total
163163
164    def _copy_unused_effects(self, other):164    def _copy_unused_effects(self, other):
165        for effect in other.effects:165        for effect in other.effects:
166            with suppress(TypeError):166            with suppress(TypeError):
167                self._add_effect(effect, other.effects[effect], other[effect].intensity)167                self._add_effect(effect, other.effects[effect], other[effect].intensity)
168168
169    def _add_effect(self, effect_name, effect, intensity=1):169    def _add_effect(self, effect_name, effect, intensity=1):
170        self[effect_name] = effect170        self[effect_name] = effect
171        self[effect_name].intensity = intensity171        self[effect_name].intensity = intensity
172172
173173
t174class Addict:t
175    def __init__(self, addict, potion):
176        self.addict = addict
177        self.potion = potion
178        self.current = addict.__dict__
179        self.changed = {}
180        self.added = set()
181 
182    def __repr__(self):
183        return f'{id(self.addict)} with state {self.current}'
184 
185 
186class ГоспожатаПоХимия:174class ГоспожатаПоХимия:
187    def __init__(self):175    def __init__(self):
188        self.addicts = {}176        self.addicts = {}
189        self.drugs = {}177        self.drugs = {}
190178
191    def apply(self, target, potion):179    def apply(self, target, potion):
192        if potion.duration:180        if potion.duration:
193            addict_id = self._make_addict(target)181            addict_id = self._make_addict(target)
194            self._add_drug(addict_id, -potion)182            self._add_drug(addict_id, -potion)
195            potion(target)183            potion(target)
196184
197    def tick(self):185    def tick(self):
198        for addict in list(self.addicts):186        for addict in list(self.addicts):
199            self._withdraw(addict)187            self._withdraw(addict)
200188
201            if not self.drugs[addict]:189            if not self.drugs[addict]:
202                self._remove(addict)190                self._remove(addict)
203                continue191                continue
204192
205            self._give_drugs(addict)193            self._give_drugs(addict)
206194
207    def _make_addict(self, target):195    def _make_addict(self, target):
208        if id(target) not in self.addicts:196        if id(target) not in self.addicts:
209            self.addicts[id(target)] = target, deepcopy(target.__dict__)197            self.addicts[id(target)] = target, deepcopy(target.__dict__)
210            self.drugs[id(target)] = deque()198            self.drugs[id(target)] = deque()
211        return id(target)199        return id(target)
212200
213    def _add_drug(self, addict_id, potion):201    def _add_drug(self, addict_id, potion):
214        if potion.duration > 0:202        if potion.duration > 0:
215            self.drugs[addict_id].append(potion)203            self.drugs[addict_id].append(potion)
216204
217    def _withdraw(self, addict_id):205    def _withdraw(self, addict_id):
218        self.addicts[addict_id][0].__dict__ = deepcopy(self.addicts[addict_id][1])206        self.addicts[addict_id][0].__dict__ = deepcopy(self.addicts[addict_id][1])
219207
220    def _give_drugs(self, addict_id):208    def _give_drugs(self, addict_id):
221        drugs = self.drugs[addict_id]209        drugs = self.drugs[addict_id]
222        addict = self.addicts[addict_id][0]210        addict = self.addicts[addict_id][0]
223        drugs_to_take = len(drugs)211        drugs_to_take = len(drugs)
224212
225        while drugs_to_take > 0:213        while drugs_to_take > 0:
226            drug = drugs.popleft()214            drug = drugs.popleft()
227            self._add_drug(addict_id, -drug)215            self._add_drug(addict_id, -drug)
228            drug(addict)216            drug(addict)
229            drugs_to_take -= 1217            drugs_to_take -= 1
230218
231    def _remove(self, addict_id):219    def _remove(self, addict_id):
232        del self.addicts[addict_id]220        del self.addicts[addict_id]
233        del self.drugs[addict_id]221        del self.drugs[addict_id]
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

f1import mathf1import math
2from contextlib import suppress2from contextlib import suppress
nn3from collections import deque
4from copy import deepcopy
35
4TRANSCENDED = 'Potion is now part of something bigger than itself.'6TRANSCENDED = 'Potion is now part of something bigger than itself.'
5DEPLETED_P = 'Potion is depleted.'7DEPLETED_P = 'Potion is depleted.'
6DEPLETED_E = 'Effect is depleted.'8DEPLETED_E = 'Effect is depleted.'
79
810
9def round_half_down(num):11def round_half_down(num):
10    return math.ceil(num - 0.5)12    return math.ceil(num - 0.5)
1113
1214
13def 使ったら消えてしまう(*msgs):15def 使ったら消えてしまう(*msgs):
14    def decorator(cls):16    def decorator(cls):
15        class UseOnce(cls):17        class UseOnce(cls):
16            def __init__(self, *args, **kwargs):18            def __init__(self, *args, **kwargs):
17                super().__init__(*args, **kwargs)19                super().__init__(*args, **kwargs)
18                for idx in range(len(msgs)):20                for idx in range(len(msgs)):
19                    setattr(self, f'__{idx}', False)21                    setattr(self, f'__{idx}', False)
2022
21            def __getattribute__(self, item):23            def __getattribute__(self, item):
22                for idx in range(len(msgs)):24                for idx in range(len(msgs)):
23                    if object.__getattribute__(self, f'__{idx}'):25                    if object.__getattribute__(self, f'__{idx}'):
24                        raise TypeError(msgs[idx])26                        raise TypeError(msgs[idx])
25                return super().__getattribute__(item)27                return super().__getattribute__(item)
2628
27        return UseOnce29        return UseOnce
2830
29    return decorator31    return decorator
3032
3133
32def 消えたら伝えろ(idx):34def 消えたら伝えろ(idx):
33    def once(method):35    def once(method):
34        def wrapper(self, other):36        def wrapper(self, other):
35            res = method(self, other)37            res = method(self, other)
36            setattr(self, f'__{idx}', True)38            setattr(self, f'__{idx}', True)
37            if type(other) is Potion:39            if type(other) is Potion:
38                setattr(other, f'__{idx}', True)40                setattr(other, f'__{idx}', True)
39            return res41            return res
4042
41        return wrapper43        return wrapper
4244
43    return once45    return once
4446
4547
46@使ったら消えてしまう(DEPLETED_E)48@使ったら消えてしまう(DEPLETED_E)
47class Effect:49class Effect:
48    @staticmethod50    @staticmethod
49    def mole_mass(effect):51    def mole_mass(effect):
50        return sum(ord(char) for char in effect)52        return sum(ord(char) for char in effect)
5153
52    def __init__(self, effect):54    def __init__(self, effect):
53        self.effect = effect55        self.effect = effect
54        self.intensity = 156        self.intensity = 1
5557
56    @消えたら伝えろ(0)58    @消えたら伝えろ(0)
57    def __call__(self, target):59    def __call__(self, target):
58        while self.intensity > 0:60        while self.intensity > 0:
59            self.effect(target)61            self.effect(target)
60            self.intensity -= 162            self.intensity -= 1
n61 n
62    def __eq__(self, other):
63        return self.effect == other.effect and self.intensity == other.intensity
6463
6564
66@使ったら消えてしまう(DEPLETED_P, TRANSCENDED)65@使ったら消えてしまう(DEPLETED_P, TRANSCENDED)
67class Potion:66class Potion:
68    def __init__(self, effects, duration):67    def __init__(self, effects, duration):
69        self.effects = effects68        self.effects = effects
70        self.duration = duration69        self.duration = duration
71        for effect_name, effect in effects.items():70        for effect_name, effect in effects.items():
72            setattr(self, effect_name, Effect(effect))71            setattr(self, effect_name, Effect(effect))
7372
74    def __getitem__(self, effect):73    def __getitem__(self, effect):
75        return getattr(self, effect)74        return getattr(self, effect)
7675
77    def __setitem__(self, effect_name, effect):76    def __setitem__(self, effect_name, effect):
78        self.effects[effect_name] = effect77        self.effects[effect_name] = effect
79        setattr(self, effect_name, Effect(effect))78        setattr(self, effect_name, Effect(effect))
8079
81    def __delitem__(self, key):80    def __delitem__(self, key):
82        delattr(self, key)81        delattr(self, key)
83        del self.effects[key]82        del self.effects[key]
8483
85    @消えたら伝えろ(1)84    @消えたら伝えろ(1)
86    def __add__(self, other):85    def __add__(self, other):
87        potion = Potion({}, max(self.duration, other.duration))86        potion = Potion({}, max(self.duration, other.duration))
88        potion._copy_unused_effects(self)87        potion._copy_unused_effects(self)
89        potion._copy_unused_effects(other)88        potion._copy_unused_effects(other)
90        for effect in set(self.effects) & set(other.effects):89        for effect in set(self.effects) & set(other.effects):
91            with suppress(TypeError):90            with suppress(TypeError):
92                potion[effect].intensity = self[effect].intensity + other[effect].intensity91                potion[effect].intensity = self[effect].intensity + other[effect].intensity
93        return potion92        return potion
9493
95    @消えたら伝えろ(1)94    @消えたら伝えろ(1)
96    def __sub__(self, other):95    def __sub__(self, other):
97        if set(other.effects) - set(self.effects):96        if set(other.effects) - set(self.effects):
98            raise TypeError97            raise TypeError
99        potion = Potion({}, self.duration)98        potion = Potion({}, self.duration)
100        potion._copy_unused_effects(self)  # do we need it?99        potion._copy_unused_effects(self)  # do we need it?
101        for effect in other.effects:100        for effect in other.effects:
102            with suppress(TypeError):101            with suppress(TypeError):
103                intensity = self[effect].intensity - other[effect].intensity102                intensity = self[effect].intensity - other[effect].intensity
104                if intensity <= 0:103                if intensity <= 0:
105                    del potion[effect]104                    del potion[effect]
106                else:105                else:
107                    potion[effect].intensity = intensity106                    potion[effect].intensity = intensity
108        return potion107        return potion
109108
110    @消えたら伝えろ(1)109    @消えたら伝えろ(1)
111    def __mul__(self, other):110    def __mul__(self, other):
112        potion = Potion({}, self.duration)111        potion = Potion({}, self.duration)
113        potion._copy_unused_effects(self)112        potion._copy_unused_effects(self)
114        for effect in self.effects:113        for effect in self.effects:
115            with suppress(TypeError):114            with suppress(TypeError):
116                potion[effect].intensity = round_half_down(self[effect].intensity * other)115                potion[effect].intensity = round_half_down(self[effect].intensity * other)
117        return potion116        return potion
118117
119    @消えたら伝えろ(1)118    @消えたら伝えろ(1)
120    def __truediv__(self, other):119    def __truediv__(self, other):
121        potions = []120        potions = []
122        for _ in range(other):121        for _ in range(other):
123            potion = Potion({}, round_half_down(self.duration / other))122            potion = Potion({}, round_half_down(self.duration / other))
124            potion._copy_unused_effects(self)123            potion._copy_unused_effects(self)
125            for effect in self.effects:124            for effect in self.effects:
126                with suppress(TypeError):125                with suppress(TypeError):
127                    potion[effect].intensity = round_half_down(self[effect].intensity / other)126                    potion[effect].intensity = round_half_down(self[effect].intensity / other)
128            potions.append(potion)127            potions.append(potion)
129        return potions128        return potions
130129
131    def __eq__(self, other):130    def __eq__(self, other):
n132        if self.effects != other.effects:n131        if self.effects.keys() != other.effects.keys():
133            return False132            return False
134        for effect in self.effects:133        for effect in self.effects:
135            intensity_1, intensity_2 = 0, 0134            intensity_1, intensity_2 = 0, 0
136            with suppress(TypeError):135            with suppress(TypeError):
137                intensity_1 = self[effect].intensity136                intensity_1 = self[effect].intensity
138            with suppress(TypeError):137            with suppress(TypeError):
139                intensity_2 = other[effect].intensity138                intensity_2 = other[effect].intensity
140            if intensity_1 != intensity_2:139            if intensity_1 != intensity_2:
141                return False140                return False
142        return True141        return True
143142
144    def __gt__(self, other):143    def __gt__(self, other):
145        return self._total_intensities() > other._total_intensities()144        return self._total_intensities() > other._total_intensities()
146145
147    @消えたら伝えろ(0)146    @消えたら伝えろ(0)
148    def __call__(self, target):147    def __call__(self, target):
149        for effect in sorted(self.effects, reverse=True, key=Effect.mole_mass):148        for effect in sorted(self.effects, reverse=True, key=Effect.mole_mass):
150            with suppress(TypeError):149            with suppress(TypeError):
151                self[effect](target)150                self[effect](target)
152151
153    def __neg__(self):152    def __neg__(self):
154        potion = Potion({}, self.duration - 1)153        potion = Potion({}, self.duration - 1)
155        potion._copy_unused_effects(self)154        potion._copy_unused_effects(self)
156        return potion155        return potion
157156
n158    def __pos__(self):n
159        potion = Potion({}, self.duration)
160        potion._copy_unused_effects(self)
161        return potion
162 
163    def _total_intensities(self):157    def _total_intensities(self):
164        total = 0158        total = 0
165        for effect in self.effects:159        for effect in self.effects:
166            with suppress(TypeError):160            with suppress(TypeError):
167                total += self[effect].intensity161                total += self[effect].intensity
168        return total162        return total
169163
170    def _copy_unused_effects(self, other):164    def _copy_unused_effects(self, other):
171        for effect in other.effects:165        for effect in other.effects:
172            with suppress(TypeError):166            with suppress(TypeError):
173                self._add_effect(effect, other.effects[effect], other[effect].intensity)167                self._add_effect(effect, other.effects[effect], other[effect].intensity)
174168
175    def _add_effect(self, effect_name, effect, intensity=1):169    def _add_effect(self, effect_name, effect, intensity=1):
176        self[effect_name] = effect170        self[effect_name] = effect
177        self[effect_name].intensity = intensity171        self[effect_name].intensity = intensity
178172
179173
180class Addict:174class Addict:
n181    def __init__(self, addict, potion, original):n175    def __init__(self, addict, potion):
182        self.addict = addict176        self.addict = addict
183        self.potion = potion177        self.potion = potion
n184        self.original = originaln
185        self.current = addict.__dict__178        self.current = addict.__dict__
186        self.changed = {}179        self.changed = {}
187        self.added = set()180        self.added = set()
188181
nn182    def __repr__(self):
183        return f'{id(self.addict)} with state {self.current}'
184 
189185
190class ГоспожатаПоХимия:186class ГоспожатаПоХимия:
n191    _addict_idx = 0n
192 
193    def __init__(self):187    def __init__(self):
194        self.addicts = {}188        self.addicts = {}
nn189        self.drugs = {}
195190
196    def apply(self, target, potion):191    def apply(self, target, potion):
197        if potion.duration:192        if potion.duration:
n198            addict_idx = self._addict(target, potion)n193            addict_id = self._make_addict(target)
194            self._add_drug(addict_id, -potion)
199            potion(target)195            potion(target)
n200            self._check_state(addict_idx)n
201196
202    def tick(self):197    def tick(self):
203        for addict in list(self.addicts):198        for addict in list(self.addicts):
204            self._withdraw(addict)199            self._withdraw(addict)
nn200 
201            if not self.drugs[addict]:
205            if self._run_out(addict):202                self._remove(addict)
206                del self.addicts[addict]
207                continue203                continue
nn204 
208            self._drug(addict)205            self._give_drugs(addict)
209206
n210    def _addict(self, target, drug):n207    def _make_addict(self, target):
211        idx = self._addict_idx208        if id(target) not in self.addicts:
212        self.addicts[idx] = Addict(target, -drug, target.__dict__.copy())209            self.addicts[id(target)] = target, deepcopy(target.__dict__)
213        self._addict_idx += 1210            self.drugs[id(target)] = deque()
214        return idx211        return id(target)
215212
n216    def _check_state(self, addict_idx):n213    def _add_drug(self, addict_id, potion):
217        addict = self.addicts[addict_idx]214        if potion.duration > 0:
215            self.drugs[addict_id].append(potion)
218216
n219        addict.added = {attr for attr in set(addict.current) - set(addict.original)}n
220        for attr in set(addict.original) & set(addict.current):
221            if addict.original[attr] != addict.current[attr]:
222                addict.changed[attr] = addict.original[attr]
223        addict.changed |= {attr: addict.original[attr] for attr in set(addict.original) - set(addict.current)}
224 
225    def _withdraw(self, addict_idx):217    def _withdraw(self, addict_id):
218        self.addicts[addict_id][0].__dict__ = deepcopy(self.addicts[addict_id][1])
219 
220    def _give_drugs(self, addict_id):
221        drugs = self.drugs[addict_id]
226        addict = self.addicts[addict_idx]222        addict = self.addicts[addict_id][0]
227        target = addict.addict223        drugs_to_take = len(drugs)
228        for attr in addict.added:
229            del target.__dict__[attr]
230        for attr in addict.changed:
231            target.__dict__[attr] = addict.changed[attr]
232224
n233    def _run_out(self, addict_idx):n225        while drugs_to_take > 0:
234        return self.addicts[addict_idx].potion.duration == 0226            drug = drugs.popleft()
227            self._add_drug(addict_id, -drug)
228            drug(addict)
229            drugs_to_take -= 1
235230
t236    def _drug(self, addict_idx):t231    def _remove(self, addict_id):
237        addict = self.addicts[addict_idx]232        del self.addicts[addict_id]
238        drug = addict.potion233        del self.drugs[addict_id]
239        addict.potion = -drug
240        drug(addict.addict)
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

f1import mathf1import math
2from contextlib import suppress2from contextlib import suppress
n3from collections import dequen
43
5TRANSCENDED = 'Potion is now part of something bigger than itself.'4TRANSCENDED = 'Potion is now part of something bigger than itself.'
6DEPLETED_P = 'Potion is depleted.'5DEPLETED_P = 'Potion is depleted.'
7DEPLETED_E = 'Effect is depleted.'6DEPLETED_E = 'Effect is depleted.'
87
98
10def round_half_down(num):9def round_half_down(num):
11    return math.ceil(num - 0.5)10    return math.ceil(num - 0.5)
1211
1312
n14def към_оня_свят(*msgs):n13def 使ったら消えてしまう(*msgs):
15    def decorator(cls):14    def decorator(cls):
16        class UseOnce(cls):15        class UseOnce(cls):
17            def __init__(self, *args, **kwargs):16            def __init__(self, *args, **kwargs):
18                super().__init__(*args, **kwargs)17                super().__init__(*args, **kwargs)
19                for idx in range(len(msgs)):18                for idx in range(len(msgs)):
20                    setattr(self, f'__{idx}', False)19                    setattr(self, f'__{idx}', False)
2120
22            def __getattribute__(self, item):21            def __getattribute__(self, item):
23                for idx in range(len(msgs)):22                for idx in range(len(msgs)):
24                    if object.__getattribute__(self, f'__{idx}'):23                    if object.__getattribute__(self, f'__{idx}'):
25                        raise TypeError(msgs[idx])24                        raise TypeError(msgs[idx])
26                return super().__getattribute__(item)25                return super().__getattribute__(item)
2726
28        return UseOnce27        return UseOnce
2928
30    return decorator29    return decorator
3130
3231
n33def преход(idx=0):n32def 消えたら伝えろ(idx):
34    def once(method):33    def once(method):
35        def wrapper(self, other):34        def wrapper(self, other):
36            res = method(self, other)35            res = method(self, other)
37            setattr(self, f'__{idx}', True)36            setattr(self, f'__{idx}', True)
38            if type(other) is Potion:37            if type(other) is Potion:
39                setattr(other, f'__{idx}', True)38                setattr(other, f'__{idx}', True)
40            return res39            return res
4140
42        return wrapper41        return wrapper
4342
44    return once43    return once
4544
4645
n47@към_оня_свят(DEPLETED_E)n46@使ったら消えてしまう(DEPLETED_E)
48class Effect:47class Effect:
49    @staticmethod48    @staticmethod
50    def mole_mass(effect):49    def mole_mass(effect):
51        return sum(ord(char) for char in effect)50        return sum(ord(char) for char in effect)
5251
53    def __init__(self, effect):52    def __init__(self, effect):
54        self.effect = effect53        self.effect = effect
55        self.intensity = 154        self.intensity = 1
5655
n57    @преход()n56    @消えたら伝えろ(0)
58    def __call__(self, target):57    def __call__(self, target):
59        while self.intensity > 0:58        while self.intensity > 0:
60            self.effect(target)59            self.effect(target)
61            self.intensity -= 160            self.intensity -= 1
6261
63    def __eq__(self, other):62    def __eq__(self, other):
64        return self.effect == other.effect and self.intensity == other.intensity63        return self.effect == other.effect and self.intensity == other.intensity
6564
6665
n67@към_оня_свят(DEPLETED_P, TRANSCENDED)n66@使ったら消えてしまう(DEPLETED_P, TRANSCENDED)
68class Potion:67class Potion:
69    def __init__(self, effects, duration):68    def __init__(self, effects, duration):
70        self.effects = effects69        self.effects = effects
71        self.duration = duration70        self.duration = duration
72        for effect_name, effect in effects.items():71        for effect_name, effect in effects.items():
73            setattr(self, effect_name, Effect(effect))72            setattr(self, effect_name, Effect(effect))
7473
75    def __getitem__(self, effect):74    def __getitem__(self, effect):
76        return getattr(self, effect)75        return getattr(self, effect)
7776
78    def __setitem__(self, effect_name, effect):77    def __setitem__(self, effect_name, effect):
79        self.effects[effect_name] = effect78        self.effects[effect_name] = effect
80        setattr(self, effect_name, Effect(effect))79        setattr(self, effect_name, Effect(effect))
8180
82    def __delitem__(self, key):81    def __delitem__(self, key):
83        delattr(self, key)82        delattr(self, key)
84        del self.effects[key]83        del self.effects[key]
8584
n86    @преход(1)n85    @消えたら伝えろ(1)
87    def __add__(self, other):86    def __add__(self, other):
88        potion = Potion({}, max(self.duration, other.duration))87        potion = Potion({}, max(self.duration, other.duration))
89        potion._copy_unused_effects(self)88        potion._copy_unused_effects(self)
90        potion._copy_unused_effects(other)89        potion._copy_unused_effects(other)
91        for effect in set(self.effects) & set(other.effects):90        for effect in set(self.effects) & set(other.effects):
92            with suppress(TypeError):91            with suppress(TypeError):
93                potion[effect].intensity = self[effect].intensity + other[effect].intensity92                potion[effect].intensity = self[effect].intensity + other[effect].intensity
94        return potion93        return potion
9594
n96    @преход(1)n95    @消えたら伝えろ(1)
97    def __sub__(self, other):96    def __sub__(self, other):
98        if set(other.effects) - set(self.effects):97        if set(other.effects) - set(self.effects):
99            raise TypeError98            raise TypeError
100        potion = Potion({}, self.duration)99        potion = Potion({}, self.duration)
101        potion._copy_unused_effects(self)  # do we need it?100        potion._copy_unused_effects(self)  # do we need it?
102        for effect in other.effects:101        for effect in other.effects:
103            with suppress(TypeError):102            with suppress(TypeError):
104                intensity = self[effect].intensity - other[effect].intensity103                intensity = self[effect].intensity - other[effect].intensity
105                if intensity <= 0:104                if intensity <= 0:
106                    del potion[effect]105                    del potion[effect]
107                else:106                else:
108                    potion[effect].intensity = intensity107                    potion[effect].intensity = intensity
109        return potion108        return potion
110109
n111    @преход(1)n110    @消えたら伝えろ(1)
112    def __mul__(self, other):111    def __mul__(self, other):
113        potion = Potion({}, self.duration)112        potion = Potion({}, self.duration)
114        potion._copy_unused_effects(self)113        potion._copy_unused_effects(self)
115        for effect in self.effects:114        for effect in self.effects:
116            with suppress(TypeError):115            with suppress(TypeError):
117                potion[effect].intensity = round_half_down(self[effect].intensity * other)116                potion[effect].intensity = round_half_down(self[effect].intensity * other)
118        return potion117        return potion
119118
n120    @преход(1)n119    @消えたら伝えろ(1)
121    def __truediv__(self, other):120    def __truediv__(self, other):
122        potions = []121        potions = []
123        for _ in range(other):122        for _ in range(other):
124            potion = Potion({}, round_half_down(self.duration / other))123            potion = Potion({}, round_half_down(self.duration / other))
125            potion._copy_unused_effects(self)124            potion._copy_unused_effects(self)
126            for effect in self.effects:125            for effect in self.effects:
127                with suppress(TypeError):126                with suppress(TypeError):
128                    potion[effect].intensity = round_half_down(self[effect].intensity / other)127                    potion[effect].intensity = round_half_down(self[effect].intensity / other)
129            potions.append(potion)128            potions.append(potion)
130        return potions129        return potions
131130
132    def __eq__(self, other):131    def __eq__(self, other):
n133        for effect in self.effects | other.effects:n132        if self.effects != other.effects:
134            try:
135                if self[effect] != other[effect]:
136                    return False133            return False
137            except AttributeError:134        for effect in self.effects:
135            intensity_1, intensity_2 = 0, 0
136            with suppress(TypeError):
137                intensity_1 = self[effect].intensity
138            with suppress(TypeError):
139                intensity_2 = other[effect].intensity
140            if intensity_1 != intensity_2:
138                return False141                return False
139        return True142        return True
140143
141    def __gt__(self, other):144    def __gt__(self, other):
142        return self._total_intensities() > other._total_intensities()145        return self._total_intensities() > other._total_intensities()
143146
n144    @преход()n147    @消えたら伝えろ(0)
145    def __call__(self, target):148    def __call__(self, target):
146        for effect in sorted(self.effects, reverse=True, key=Effect.mole_mass):149        for effect in sorted(self.effects, reverse=True, key=Effect.mole_mass):
147            with suppress(TypeError):150            with suppress(TypeError):
148                self[effect](target)151                self[effect](target)
149152
150    def __neg__(self):153    def __neg__(self):
151        potion = Potion({}, self.duration - 1)154        potion = Potion({}, self.duration - 1)
nn155        potion._copy_unused_effects(self)
156        return potion
157 
158    def __pos__(self):
159        potion = Potion({}, self.duration)
152        potion._copy_unused_effects(self)160        potion._copy_unused_effects(self)
153        return potion161        return potion
154162
155    def _total_intensities(self):163    def _total_intensities(self):
156        total = 0164        total = 0
157        for effect in self.effects:165        for effect in self.effects:
158            with suppress(TypeError):166            with suppress(TypeError):
159                total += self[effect].intensity167                total += self[effect].intensity
160        return total168        return total
161169
162    def _copy_unused_effects(self, other):170    def _copy_unused_effects(self, other):
163        for effect in other.effects:171        for effect in other.effects:
164            with suppress(TypeError):172            with suppress(TypeError):
165                self._add_effect(effect, other.effects[effect], other[effect].intensity)173                self._add_effect(effect, other.effects[effect], other[effect].intensity)
166174
167    def _add_effect(self, effect_name, effect, intensity=1):175    def _add_effect(self, effect_name, effect, intensity=1):
168        self[effect_name] = effect176        self[effect_name] = effect
169        self[effect_name].intensity = intensity177        self[effect_name].intensity = intensity
170178
171179
nn180class Addict:
181    def __init__(self, addict, potion, original):
182        self.addict = addict
183        self.potion = potion
184        self.original = original
185        self.current = addict.__dict__
186        self.changed = {}
187        self.added = set()
188 
189 
172class ГоспожатаПоХимия:190class ГоспожатаПоХимия:
nn191    _addict_idx = 0
192 
173    def __init__(self):193    def __init__(self):
174        self.addicts = {}194        self.addicts = {}
n175        self.drugs = {}n
176195
177    def apply(self, target, potion):196    def apply(self, target, potion):
nn197        if potion.duration:
178        addict_id = self._make_addict(target)198            addict_idx = self._addict(target, potion)
179        self._add_drug(addict_id, -potion)
180        potion(target)199            potion(target)
200            self._check_state(addict_idx)
181201
182    def tick(self):202    def tick(self):
n183        for addict in self.addicts:n203        for addict in list(self.addicts):
184            self._withdraw(addict)204            self._withdraw(addict)
nn205            if self._run_out(addict):
206                del self.addicts[addict]
207                continue
185            self._give_drugs(addict)208            self._drug(addict)
186209
n187    def _make_addict(self, target):n210    def _addict(self, target, drug):
188        if id(target) not in self.addicts:211        idx = self._addict_idx
189            self.addicts[id(target)] = target, target.__dict__.copy()212        self.addicts[idx] = Addict(target, -drug, target.__dict__.copy())
190            self.drugs[id(target)] = deque()213        self._addict_idx += 1
191        return id(target)214        return idx
192215
n193    def _add_drug(self, addict_id, potion):n216    def _check_state(self, addict_idx):
194        if potion.duration > 0:217        addict = self.addicts[addict_idx]
195            self.drugs[addict_id].append(potion)
196218
tt219        addict.added = {attr for attr in set(addict.current) - set(addict.original)}
220        for attr in set(addict.original) & set(addict.current):
221            if addict.original[attr] != addict.current[attr]:
222                addict.changed[attr] = addict.original[attr]
223        addict.changed |= {attr: addict.original[attr] for attr in set(addict.original) - set(addict.current)}
224 
197    def _withdraw(self, addict_id):225    def _withdraw(self, addict_idx):
198        self.addicts[addict_id][0].__dict__ = self.addicts[addict_id][1].copy()
199 
200    def _give_drugs(self, addict_id):
201        drugs = self.drugs[addict_id]
202        addict = self.addicts[addict_id][0]226        addict = self.addicts[addict_idx]
203        drugs_to_take = len(drugs)227        target = addict.addict
204        while drugs_to_take > 0:228        for attr in addict.added:
205            drug = drugs.popleft()229            del target.__dict__[attr]
206            self._add_drug(addict_id, -drug)230        for attr in addict.changed:
231            target.__dict__[attr] = addict.changed[attr]
232 
233    def _run_out(self, addict_idx):
234        return self.addicts[addict_idx].potion.duration == 0
235 
236    def _drug(self, addict_idx):
237        addict = self.addicts[addict_idx]
238        drug = addict.potion
239        addict.potion = -drug
207            drug(addict)240        drug(addict.addict)
208            drugs_to_take -= 1
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

nn1import math
2from contextlib import suppress
3from collections import deque
4 
5TRANSCENDED = 'Potion is now part of something bigger than itself.'
6DEPLETED_P = 'Potion is depleted.'
7DEPLETED_E = 'Effect is depleted.'
8 
9 
10def round_half_down(num):
11    return math.ceil(num - 0.5)
12 
13 
14def към_оня_свят(*msgs):
15    def decorator(cls):
16        class UseOnce(cls):
17            def __init__(self, *args, **kwargs):
18                super().__init__(*args, **kwargs)
19                for idx in range(len(msgs)):
20                    setattr(self, f'__{idx}', False)
21 
22            def __getattribute__(self, item):
23                for idx in range(len(msgs)):
24                    if object.__getattribute__(self, f'__{idx}'):
25                        raise TypeError(msgs[idx])
26                return super().__getattribute__(item)
27 
28        return UseOnce
29 
30    return decorator
31 
32 
33def преход(idx=0):
34    def once(method):
35        def wrapper(self, other):
36            res = method(self, other)
37            setattr(self, f'__{idx}', True)
38            if type(other) is Potion:
39                setattr(other, f'__{idx}', True)
40            return res
41 
42        return wrapper
43 
44    return once
45 
46 
47@към_оня_свят(DEPLETED_E)
48class Effect:
49    @staticmethod
50    def mole_mass(effect):
51        return sum(ord(char) for char in effect)
52 
53    def __init__(self, effect):
54        self.effect = effect
55        self.intensity = 1
56 
57    @преход()
58    def __call__(self, target):
59        while self.intensity > 0:
60            self.effect(target)
61            self.intensity -= 1
62 
63    def __eq__(self, other):
64        return self.effect == other.effect and self.intensity == other.intensity
65 
66 
67@към_оня_свят(DEPLETED_P, TRANSCENDED)
1class Potion:68class Potion:
t2    passt69    def __init__(self, effects, duration):
70        self.effects = effects
71        self.duration = duration
72        for effect_name, effect in effects.items():
73            setattr(self, effect_name, Effect(effect))
74 
75    def __getitem__(self, effect):
76        return getattr(self, effect)
77 
78    def __setitem__(self, effect_name, effect):
79        self.effects[effect_name] = effect
80        setattr(self, effect_name, Effect(effect))
81 
82    def __delitem__(self, key):
83        delattr(self, key)
84        del self.effects[key]
85 
86    @преход(1)
87    def __add__(self, other):
88        potion = Potion({}, max(self.duration, other.duration))
89        potion._copy_unused_effects(self)
90        potion._copy_unused_effects(other)
91        for effect in set(self.effects) & set(other.effects):
92            with suppress(TypeError):
93                potion[effect].intensity = self[effect].intensity + other[effect].intensity
94        return potion
95 
96    @преход(1)
97    def __sub__(self, other):
98        if set(other.effects) - set(self.effects):
99            raise TypeError
100        potion = Potion({}, self.duration)
101        potion._copy_unused_effects(self)  # do we need it?
102        for effect in other.effects:
103            with suppress(TypeError):
104                intensity = self[effect].intensity - other[effect].intensity
105                if intensity <= 0:
106                    del potion[effect]
107                else:
108                    potion[effect].intensity = intensity
109        return potion
110 
111    @преход(1)
112    def __mul__(self, other):
113        potion = Potion({}, self.duration)
114        potion._copy_unused_effects(self)
115        for effect in self.effects:
116            with suppress(TypeError):
117                potion[effect].intensity = round_half_down(self[effect].intensity * other)
118        return potion
119 
120    @преход(1)
121    def __truediv__(self, other):
122        potions = []
123        for _ in range(other):
124            potion = Potion({}, round_half_down(self.duration / other))
125            potion._copy_unused_effects(self)
126            for effect in self.effects:
127                with suppress(TypeError):
128                    potion[effect].intensity = round_half_down(self[effect].intensity / other)
129            potions.append(potion)
130        return potions
131 
132    def __eq__(self, other):
133        for effect in self.effects | other.effects:
134            try:
135                if self[effect] != other[effect]:
136                    return False
137            except AttributeError:
138                return False
139        return True
140 
141    def __gt__(self, other):
142        return self._total_intensities() > other._total_intensities()
143 
144    @преход()
145    def __call__(self, target):
146        for effect in sorted(self.effects, reverse=True, key=Effect.mole_mass):
147            with suppress(TypeError):
148                self[effect](target)
149 
150    def __neg__(self):
151        potion = Potion({}, self.duration - 1)
152        potion._copy_unused_effects(self)
153        return potion
154 
155    def _total_intensities(self):
156        total = 0
157        for effect in self.effects:
158            with suppress(TypeError):
159                total += self[effect].intensity
160        return total
161 
162    def _copy_unused_effects(self, other):
163        for effect in other.effects:
164            with suppress(TypeError):
165                self._add_effect(effect, other.effects[effect], other[effect].intensity)
166 
167    def _add_effect(self, effect_name, effect, intensity=1):
168        self[effect_name] = effect
169        self[effect_name].intensity = intensity
170 
171 
172class ГоспожатаПоХимия:
173    def __init__(self):
174        self.addicts = {}
175        self.drugs = {}
176 
177    def apply(self, target, potion):
178        addict_id = self._make_addict(target)
179        self._add_drug(addict_id, -potion)
180        potion(target)
181 
182    def tick(self):
183        for addict in self.addicts:
184            self._withdraw(addict)
185            self._give_drugs(addict)
186 
187    def _make_addict(self, target):
188        if id(target) not in self.addicts:
189            self.addicts[id(target)] = target, target.__dict__.copy()
190            self.drugs[id(target)] = deque()
191        return id(target)
192 
193    def _add_drug(self, addict_id, potion):
194        if potion.duration > 0:
195            self.drugs[addict_id].append(potion)
196 
197    def _withdraw(self, addict_id):
198        self.addicts[addict_id][0].__dict__ = self.addicts[addict_id][1].copy()
199 
200    def _give_drugs(self, addict_id):
201        drugs = self.drugs[addict_id]
202        addict = self.addicts[addict_id][0]
203        drugs_to_take = len(drugs)
204        while drugs_to_take > 0:
205            drug = drugs.popleft()
206            self._add_drug(addict_id, -drug)
207            drug(addict)
208            drugs_to_take -= 1
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op