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 | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|