1class Material:
2 def __init__(self, mass: int, density: int):
3 self.mass = mass
4 self._density = density
5 self._valid = True
6
7 @property
8 def volume(self):
9 return self.mass / self._density
10
11 def invalidate(self):
12 self._valid = False
13
14 def is_valid(self):
15 return self._valid
16
17def create_material_class(name, density):
18 return type(
19 name,
20 (Material,),
21 {
22 "__init__": lambda self, mass: super(type(self), self).__init__(mass, density)
23 }
24 )
25
26class Factory:
27 _dynamic_classes = {}
28 _created_objects = []
29 _material_classes = {}
30
31 def __init__(self):
32 self._materials = []
33
34 @classmethod
35 def register_material_class(cls, name, material_class):
36 cls._material_classes[name] = material_class
37
38 def __call__(self, *args, **kwargs):
39 if args and kwargs:
40 raise ValueError("Cannot mix positional and keyword arguments.")
41 if not args and not kwargs:
42 raise ValueError("Cannot call factory without arguments.")
43
44 if args:
45 return self._handle_positional(args)
46 elif kwargs:
47 return self._handle_keyword(kwargs)
48
49 def _handle_positional(self, args):
50 if any(not material.is_valid() for material in args):
51 raise AssertionError("Material has already been used.")
52
53 class_names = sorted(type(arg).__name__ for arg in args)
54 combined_material_name = "_".join(class_names)
55
56 if combined_material_name not in Factory._dynamic_classes:
57 avg_density = sum(arg._density for arg in args) / len(args)
58
59 class DynamicMaterial(Material):
60 def __init__(self, mass: int):
61 super().__init__(mass, avg_density)
62
63 DynamicMaterial.__name__ = combined_material_name
64 Factory._dynamic_classes[combined_material_name] = DynamicMaterial
65
66 for material in args:
67 material.invalidate()
68
69 new_class = Factory._dynamic_classes[combined_material_name]
70 mass = sum(material.mass for material in args)
71 instance = new_class(mass)
72 self._materials.append(instance)
73
74 return instance
75
76 def _handle_keyword(self, kwargs):
77 instances = []
78 for key, mass in kwargs.items():
79 if key not in self._material_classes:
80 raise ValueError(f"Unknown material type: {key}")
81 material_class = self._material_classes[key]
82 instance = material_class(mass)
83 instances.append(instance)
84 self._materials.append(instance)
85 return tuple(instances)
86
87 def can_build(self, volume: int):
88 total_volume = sum(mat.volume for mat in self._materials if mat.is_valid())
89 return total_volume >= volume
90
91 @classmethod
92 def can_build_together(cls, volume: int):
93 total_volume = sum(mat.volume for mat in cls._created_objects if mat.is_valid())
94 return total_volume >= volume
95
96Concrete = create_material_class("Concrete", 2500)
97Brick = create_material_class("Brick", 2000)
98Stone = create_material_class("Stone", 1600)
99Wood = create_material_class("Wood", 600)
100Steel = create_material_class("Steel", 7700)
101
102Factory.register_material_class("Concrete", Concrete)
103Factory.register_material_class("Brick", Brick)
104Factory.register_material_class("Stone", Stone)
105Factory.register_material_class("Wood", Wood)
106Factory.register_material_class("Steel", Steel)
.FE.E.FF..
======================================================================
ERROR: test_materials_between_factories (test.TestFactory.test_materials_between_factories)
Test materials sharing.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 156, in test_materials_between_factories
result2, = self.factory2(Brick_Concrete_Steel_Stone_Wood=2)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 47, in __call__
return self._handle_keyword(kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 80, in _handle_keyword
raise ValueError(f"Unknown material type: {key}")
ValueError: Unknown material type: Brick_Concrete_Steel_Stone_Wood
======================================================================
ERROR: test_named_arguments_with_dynamically_created_classes (test.TestFactory.test_named_arguments_with_dynamically_created_classes)
Test dynamically created classes uniqueness.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 145, in test_named_arguments_with_dynamically_created_classes
result2, = self.factory1(Brick_Concrete_Steel_Stone_Wood=2)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 47, in __call__
return self._handle_keyword(kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 80, in _handle_keyword
raise ValueError(f"Unknown material type: {key}")
ValueError: Unknown material type: Brick_Concrete_Steel_Stone_Wood
======================================================================
FAIL: test_can_build (test.TestFactory.test_can_build)
Test can_build methods.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 178, in test_can_build
self.assertTrue(self.factory2.can_build_together(6.0))
AssertionError: False is not true
======================================================================
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: 'Brick_Concrete_Stone_Steel_Wood' != 'Brick_Concrete_Steel_Stone_Wood'
- Brick_Concrete_Stone_Steel_Wood
? ------
+ Brick_Concrete_Steel_Stone_Wood
? ++++++
======================================================================
FAIL: test_positional_arguments_single_argument (test.TestFactory.test_positional_arguments_single_argument)
Test calling a factory using a sigle positional argument.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 80, in test_positional_arguments_single_argument
self.assertIs(type(concrete2), solution.Concrete)
AssertionError: <class 'solution.Factory._handle_positional.<locals>.DynamicMaterial'> is not <class 'solution.Concrete'>
----------------------------------------------------------------------
Ran 10 tests in 0.011s
FAILED (failures=3, errors=2)
| f | 1 | class Material: | f | 1 | class Material: |
| 2 | def __init__(self, mass: int, density: int): | 2 | def __init__(self, mass: int, density: int): | ||
| 3 | self.mass = mass | 3 | self.mass = mass | ||
| 4 | self._density = density | 4 | self._density = density | ||
| 5 | self._valid = True | 5 | self._valid = True | ||
| 6 | 6 | ||||
| 7 | @property | 7 | @property | ||
| 8 | def volume(self): | 8 | def volume(self): | ||
| 9 | return self.mass / self._density | 9 | return self.mass / self._density | ||
| 10 | 10 | ||||
| 11 | def invalidate(self): | 11 | def invalidate(self): | ||
| 12 | self._valid = False | 12 | self._valid = False | ||
| 13 | 13 | ||||
| 14 | def is_valid(self): | 14 | def is_valid(self): | ||
| 15 | return self._valid | 15 | return self._valid | ||
| 16 | 16 | ||||
| n | 17 | n | 17 | def create_material_class(name, density): | |
| 18 | class Concrete(Material): | 18 | return type( | ||
| 19 | def __init__(self, mass: int): | 19 | name, | ||
| 20 | super().__init__(mass, 2500) | 20 | (Material,), | ||
| 21 | 21 | { | |||
| 22 | 22 | "__init__": lambda self, mass: super(type(self), self).__init__(mass, density) | |||
| 23 | class Brick(Material): | 23 | } | ||
| 24 | def __init__(self, mass: int): | 24 | ) | ||
| 25 | super().__init__(mass, 2000) | ||||
| 26 | |||||
| 27 | |||||
| 28 | class Stone(Material): | ||||
| 29 | def __init__(self, mass: int): | ||||
| 30 | super().__init__(mass, 1600) | ||||
| 31 | |||||
| 32 | |||||
| 33 | class Wood(Material): | ||||
| 34 | def __init__(self, mass: int): | ||||
| 35 | super().__init__(mass, 600) | ||||
| 36 | |||||
| 37 | |||||
| 38 | class Steel(Material): | ||||
| 39 | def __init__(self, mass: int): | ||||
| 40 | super().__init__(mass, 7700) | ||||
| 41 | |||||
| 42 | 25 | ||||
| 43 | class Factory: | 26 | class Factory: | ||
| 44 | _dynamic_classes = {} | 27 | _dynamic_classes = {} | ||
| 45 | _created_objects = [] | 28 | _created_objects = [] | ||
| n | n | 29 | _material_classes = {} | ||
| 46 | 30 | ||||
| 47 | def __init__(self): | 31 | def __init__(self): | ||
| 48 | self._materials = [] | 32 | self._materials = [] | ||
| n | n | 33 | |||
| 34 | @classmethod | ||||
| 35 | def register_material_class(cls, name, material_class): | ||||
| 36 | cls._material_classes[name] = material_class | ||||
| 49 | 37 | ||||
| 50 | def __call__(self, *args, **kwargs): | 38 | def __call__(self, *args, **kwargs): | ||
| 51 | if args and kwargs: | 39 | if args and kwargs: | ||
| 52 | raise ValueError("Cannot mix positional and keyword arguments.") | 40 | raise ValueError("Cannot mix positional and keyword arguments.") | ||
| 53 | if not args and not kwargs: | 41 | if not args and not kwargs: | ||
| 54 | raise ValueError("Cannot call factory without arguments.") | 42 | raise ValueError("Cannot call factory without arguments.") | ||
| 55 | 43 | ||||
| n | 56 | if args: | n | 44 | if args: |
| 57 | return self._handle_positional(args) | 45 | return self._handle_positional(args) | ||
| n | 58 | elif kwargs: | n | 46 | elif kwargs: |
| 59 | return self._handle_keyword(kwargs) | 47 | return self._handle_keyword(kwargs) | ||
| 60 | 48 | ||||
| 61 | def _handle_positional(self, args): | 49 | def _handle_positional(self, args): | ||
| n | 62 | for material in args: | n | 50 | if any(not material.is_valid() for material in args): |
| 63 | if not material.is_valid(): | ||||
| 64 | raise AssertionError("Material has already been used.") | 51 | raise AssertionError("Material has already been used.") | ||
| 52 | |||||
| 65 | class_names = sorted(type(arg).__name__ for arg in args) | 53 | class_names = sorted(type(arg).__name__ for arg in args) | ||
| n | 66 | class_key = "_".join(class_names) | n | 54 | combined_material_name = "_".join(class_names) |
| 55 | |||||
| 67 | if class_key not in Factory._dynamic_classes: | 56 | if combined_material_name not in Factory._dynamic_classes: | ||
| 68 | avg_density = sum(arg._density for arg in args) / len(args) | 57 | avg_density = sum(arg._density for arg in args) / len(args) | ||
| 69 | 58 | ||||
| 70 | class DynamicMaterial(Material): | 59 | class DynamicMaterial(Material): | ||
| 71 | def __init__(self, mass: int): | 60 | def __init__(self, mass: int): | ||
| 72 | super().__init__(mass, avg_density) | 61 | super().__init__(mass, avg_density) | ||
| 73 | 62 | ||||
| n | 74 | DynamicMaterial.__name__ = class_key | n | 63 | DynamicMaterial.__name__ = combined_material_name |
| 75 | Factory._dynamic_classes[class_key] = DynamicMaterial | 64 | Factory._dynamic_classes[combined_material_name] = DynamicMaterial | ||
| 76 | 65 | ||||
| 77 | for material in args: | 66 | for material in args: | ||
| 78 | material.invalidate() | 67 | material.invalidate() | ||
| n | n | 68 | |||
| 79 | new_class = Factory._dynamic_classes[class_key] | 69 | new_class = Factory._dynamic_classes[combined_material_name] | ||
| 80 | mass = sum(material.mass for material in args) | 70 | mass = sum(material.mass for material in args) | ||
| 81 | instance = new_class(mass) | 71 | instance = new_class(mass) | ||
| 82 | self._materials.append(instance) | 72 | self._materials.append(instance) | ||
| n | n | 73 | |||
| 83 | return instance | 74 | return instance | ||
| 84 | 75 | ||||
| 85 | def _handle_keyword(self, kwargs): | 76 | def _handle_keyword(self, kwargs): | ||
| 86 | instances = [] | 77 | instances = [] | ||
| 87 | for key, mass in kwargs.items(): | 78 | for key, mass in kwargs.items(): | ||
| n | 88 | if key not in globals(): | n | 79 | if key not in self._material_classes: |
| 89 | raise ValueError(f"Unknown material type: {key}") | 80 | raise ValueError(f"Unknown material type: {key}") | ||
| n | 90 | material_class = globals()[key] | n | 81 | material_class = self._material_classes[key] |
| 91 | instance = material_class(mass) | 82 | instance = material_class(mass) | ||
| 92 | instances.append(instance) | 83 | instances.append(instance) | ||
| 93 | self._materials.append(instance) | 84 | self._materials.append(instance) | ||
| 94 | return tuple(instances) | 85 | return tuple(instances) | ||
| 95 | 86 | ||||
| 96 | def can_build(self, volume: int): | 87 | def can_build(self, volume: int): | ||
| 97 | total_volume = sum(mat.volume for mat in self._materials if mat.is_valid()) | 88 | total_volume = sum(mat.volume for mat in self._materials if mat.is_valid()) | ||
| 98 | return total_volume >= volume | 89 | return total_volume >= volume | ||
| 99 | 90 | ||||
| 100 | @classmethod | 91 | @classmethod | ||
| 101 | def can_build_together(cls, volume: int): | 92 | def can_build_together(cls, volume: int): | ||
| 102 | total_volume = sum(mat.volume for mat in cls._created_objects if mat.is_valid()) | 93 | total_volume = sum(mat.volume for mat in cls._created_objects if mat.is_valid()) | ||
| 103 | return total_volume >= volume | 94 | return total_volume >= volume | ||
| t | t | 95 | |||
| 96 | Concrete = create_material_class("Concrete", 2500) | ||||
| 97 | Brick = create_material_class("Brick", 2000) | ||||
| 98 | Stone = create_material_class("Stone", 1600) | ||||
| 99 | Wood = create_material_class("Wood", 600) | ||||
| 100 | Steel = create_material_class("Steel", 7700) | ||||
| 101 | |||||
| 102 | Factory.register_material_class("Concrete", Concrete) | ||||
| 103 | Factory.register_material_class("Brick", Brick) | ||||
| 104 | Factory.register_material_class("Stone", Stone) | ||||
| 105 | Factory.register_material_class("Wood", Wood) | ||||
| 106 | Factory.register_material_class("Steel", Steel) |
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
| |||||||||