Домашни > Another brick in the wall > Решения > Решението на Димитър Фенерски

Резултати
9 точки от тестове
0 точки от учител

9 точки общо

9 успешни теста
1 неуспешни теста
Код
Скрий всички коментари

  1# type: ignore
  2
  3from abc import ABC, abstractmethod
  4from functools import reduce
  5
  6
  7class ErrorUtils:
  8    @staticmethod
  9    def raise_hw_err():
 10        raise ValueError("произволен текст")
 11
 12
 13class Mine:
 14    _registry = dict()
 15
 16    @staticmethod
 17    def get_material(material):
 18        return Mine._registry.get(material)
 19
 20    @staticmethod
 21    def add_material(name, cls, with_error=False):
 22        if Mine.get_material(name):
 23            if with_error:
 24                ErrorUtils.raise_hw_err()
 25        Mine._registry.update({name: cls})
 26        return Mine.get_material(name)
 27
 28
 29class Material:
 30    density = 1
 31
 32    def __init__(self, mass):
 33        self.mass = mass
 34        self.is_usable = True
 35        self.atomic_types = frozenset()
 36        Mine.add_material(self.__class__.__name__, self.__class__)
 37
 38    @property
 39    def volume(self):
 40        return self.mass / self.density
 41
 42
 43class Concrete(Material):
 44    density = 2500
 45
 46    def __init__(self, mass):
 47        super().__init__(mass)
 48        self.atomic_types = frozenset([self.__class__.__name__])
 49
 50
 51class Brick(Material):
 52    density = 2000
 53
 54    def __init__(self, mass):
 55        super().__init__(mass)
 56        self.atomic_types = frozenset([self.__class__.__name__])
 57
 58
 59class Stone(Material):
 60    density = 1600
 61
 62    def __init__(self, mass):
 63        super().__init__(mass)
 64        self.atomic_types = frozenset([self.__class__.__name__])
 65
 66
 67class Wood(Material):
 68    density = 600
 69
 70    def __init__(self, mass):
 71        super().__init__(mass)
 72        self.atomic_types = frozenset([self.__class__.__name__])
 73
 74
 75class Steel(Material):
 76    density = 7700
 77
 78    def __init__(self, mass):
 79        super().__init__(mass)
 80        self.atomic_types = frozenset([self.__class__.__name__])
 81
 82
 83Mine.add_material(Concrete.__name__, Concrete)
 84Mine.add_material(Brick.__name__, Brick)
 85Mine.add_material(Stone.__name__, Stone)
 86Mine.add_material(Wood.__name__, Wood)
 87Mine.add_material(Steel.__name__, Steel)
 88
 89
 90class ProductionStrategy(ABC):
 91    @abstractmethod
 92    def _validate_input(self, *args, **kwargs):
 93        pass
 94
 95    @abstractmethod
 96    def produce_new(self, *args, **kwargs):
 97        pass
 98
 99
100class NamedProductionStrategy(ProductionStrategy):
101    def _validate_input(fn):
102        def validated(self, kwargs):
103            for material_name in kwargs:
104
105                cls = Mine.get_material(material_name)
106
107                if not cls:
108                    ErrorUtils.raise_hw_err()
109
110            return fn(self, kwargs)
111        return validated
112
113    @_validate_input
114    def produce_new(self, kwargs):
115        return tuple(
116            [Mine.get_material(material_name)(kwargs[material_name]) for material_name in kwargs]
117        )
118
119
120class PositionalProductionStrategy(ProductionStrategy):
121    def _validate_input(fn):
122        def validated(self, args):
123            for maybe_material_instance in args:
124                cls = Mine.get_material(maybe_material_instance.__class__.__name__)
125
126                if not cls:
127                    ErrorUtils.raise_hw_err()
128
129                if not isinstance(maybe_material_instance, cls):
130                    ErrorUtils.raise_hw_err()
131
132                assert maybe_material_instance.is_usable
133            return fn(self, args)
134        return validated
135
136    @_validate_input
137    def produce_new(self, args):
138        alloy_atomic_types = reduce(lambda aggr, material: aggr.union(material.atomic_types), [material for material in args], frozenset())
139        alloy_signature = "_".join([atomic_type for atomic_type in sorted(alloy_atomic_types, key=str.casefold)])
140
141        alloy_cls = None
142        if existing_alloy_cls := Mine.get_material(alloy_signature):
143            alloy_cls = existing_alloy_cls
144        else:
145            alloy_cls = type(
146                alloy_signature,
147                tuple([Material]),
148                {
149                    "density": sum([Mine.get_material(material_cls).density for material_cls in alloy_atomic_types]) / len(alloy_atomic_types),
150                    "atomic_types": alloy_atomic_types
151                }
152            )
153
154        Mine.add_material(alloy_signature, alloy_cls)
155
156        alloy = alloy_cls(sum([material.mass for material in args]))
157
158        for material in args:
159            material.is_usable = False
160
161        return alloy
162
163
164class Factory:
165    @staticmethod
166    def _validate_factory_call(fn):
167        def validated(self, *args, **kwargs):
168            if len(args) == 0 and len(kwargs) == 0:
169                ErrorUtils.raise_hw_err()
170            if len(args) > 0 and len(kwargs) > 0:
171                ErrorUtils.raise_hw_err()
172            return fn(self, *args, **kwargs)
173        return validated
174
175    _registry = dict()
176
177    def __init__(self):
178        Factory._registry.update({id(self): self})
179        self.materials = []
180
181    @_validate_factory_call
182    def __call__(self, *args, **kwargs):
183        strategy, payload = (PositionalProductionStrategy(), args) if len(args) else (NamedProductionStrategy(), kwargs)
184
185        batch = strategy.produce_new(payload)
186
187        if len(kwargs):
188            for material in batch:
189                self.materials.append(material)
190        else:
191            self.materials.append(batch)
192
193        return batch
194
195    def can_build(self, volume):
196        return reduce(lambda acc, material: acc + material.volume, [material for material in self.materials if material.is_usable], 0) >= volume
197
198    @classmethod
199    def can_build_together(self, volume):
200        return reduce(lambda acc, material: acc + material.volume, [material for factory_key in Factory._registry for material in Factory._registry[factory_key].materials if material.is_usable], 0) >= volume

......F...
======================================================================
FAIL: test_positional_arguments_multiple_argument_with_dynamics (test.TestFactory.test_positional_arguments_multiple_argument_with_dynamics)
Test calling a factory using multiple positional arguments.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 128, in test_positional_arguments_multiple_argument_with_dynamics
self.assertEqual(mega_material.__class__.__name__,
AssertionError: 'Steel_Wood' != 'Brick_Concrete_Steel_Stone_Wood'
- Steel_Wood
+ Brick_Concrete_Steel_Stone_Wood

----------------------------------------------------------------------
Ran 10 tests in 0.016s

FAILED (failures=1)

Дискусия
История
Това решение има само една версия.