1class Material:
2 """ A PARENT CLASS SO AS TO AVOID WRITING THE SAME CODE """
3 _DENSITY = 1
4
5 def __init__(self, mass):
6 self.mass = mass
7 self._is_valid = True # mark the material as valid to use (for the Alloy class)
8
9 @property
10 def volume(self):
11 return self.mass / self._DENSITY
12
13 def invalidate(self):
14 """Mark this instance as invalid"""
15 self._is_valid = False
16
17 def assert_valid(self):
18 """Ensure this instance is still valid"""
19 assert self._is_valid, "This material instance is no longer valid for use"
20
21
22class Concrete(Material):
23 _DENSITY = 2500
24
25
26class Brick(Material):
27 _DENSITY = 2000
28
29
30class Stone(Material):
31 _DENSITY = 1600
32
33
34class Wood(Material):
35 _DENSITY = 600
36
37
38class Steel(Material):
39 _DENSITY = 7700
40
41
42class Alloy(Material):
43 _created_classes = {}
44
45 def __new__(cls, *Materials):
46 # ensure that all materials are valid before using them
47 if not Materials: #because I got a few cases of / by 0
48 return
49 for m in Materials:
50 m.assert_valid()
51 all_base_materials = set()
52 for m in Materials:
53 if isinstance(m, Alloy):
54 all_base_materials.update(m._materials)
55 else:
56 all_base_materials.add(m)
57 material_classes = sorted(type(m).__name__ for m in all_base_materials)
58 class_name = "_".join(material_classes)
59 #check if the class already exists
60 if class_name not in cls._created_classes:
61 density = sum(m._DENSITY for m in all_base_materials) / len(all_base_materials)
62 new_class = type(class_name, (cls,), {
63 '_DENSITY': density,
64 '_materials': tuple(all_base_materials)
65 })
66 cls._created_classes[class_name] = new_class
67
68 return super().__new__(cls._created_classes[class_name])
69
70 def __init__(self, *Materials):
71 #avoid reinitialization if the instance has already been created
72 if hasattr(self, '_initialized') and self._initialized:
73 return
74 #invalidate the materials used
75 for m in Materials:
76 m.invalidate()
77 self.mass = sum(m.mass for m in Materials)
78 self._is_valid = True #mark the instance as valid
79 self._initialized = True
80
81
82class Factory:
83 _instances = []
84
85 def __init__(self):
86 self._materials = [] #so as to keep track and make the serialization work correctly for multiple parents
87 Factory._instances.append(self)
88
89 def __serialize(self, key, val):
90 material_classes = {
91 'Concrete': Concrete,
92 'Brick': Brick,
93 'Stone': Stone,
94 'Wood': Wood,
95 'Steel': Steel
96 }
97 if key in material_classes:
98 material = material_classes[key](val)
99 self._materials.append(material)
100 return [material] #returning an iterable becuase -> error
101 if key in Alloy._created_classes:
102 cls = Alloy._created_classes[key]
103 material = cls.__new__(cls)
104 material.__init__(val)
105 self._materials.append(material)
106 return [material]
107 raise ValueError(f"Invalid key {key}")
108
109 def __call__(self, *args, **kwargs):
110 if (len(args) > 0 and len(kwargs) > 0) or (len(args) == 0 and len(kwargs) == 0):
111 raise ValueError("The given arguments are invalid")
112 elif len(kwargs) > 0:
113 res = []
114 for key in kwargs:
115 res.extend(self.__serialize(key, kwargs[key]))
116 return tuple(res)
117 elif len(args) > 0:
118 material = Alloy(*args)
119 self._materials.append(material)
120 return material
121
122 def can_build(self, required_volume):
123 total_volume = sum(m.volume for m in self._materials if m._is_valid)
124 return total_volume >= required_volume
125
126 @classmethod
127 def can_build_together(cls, required_volume):
128 total_volume = sum(
129 m.volume for factory in cls._instances for m in factory._materials if m._is_valid
130 )
131 return total_volume >= required_volume
..F.FE.F..
======================================================================
ERROR: test_positional_arguments_multiple_argument_from_initial_set (test.TestFactory.test_positional_arguments_multiple_argument_from_initial_set)
Test calling a factory using multiple positional arguments.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 97, in test_positional_arguments_multiple_argument_from_initial_set
brick_concrete_wood = type(result)(1700)
^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 50, in __new__
m.assert_valid()
^^^^^^^^^^^^^^
AttributeError: 'int' object has no attribute 'assert_valid'
======================================================================
FAIL: test_materials_between_factories (test.TestFactory.test_materials_between_factories)
Test materials sharing.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 157, in test_materials_between_factories
self.assertIs(type(result1), type(result2))
AssertionError: <class 'solution.Brick_Concrete_Steel_Stone_Wood'> is not <class 'NoneType'>
======================================================================
FAIL: 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 148, in test_named_arguments_with_dynamically_created_classes
self.assertIs(type(result1), type(result2))
AssertionError: <class 'solution.Brick_Concrete_Steel_Stone_Wood'> is not <class 'NoneType'>
======================================================================
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.Concrete'> is not <class 'solution.Concrete'>
----------------------------------------------------------------------
Ran 10 tests in 0.047s
FAILED (failures=3, errors=1)
26.11.2024 13:55
26.11.2024 14:03
26.11.2024 13:56
26.11.2024 13:56
26.11.2024 13:59
26.11.2024 13:58
26.11.2024 14:04
26.11.2024 14:04
26.11.2024 14:05
26.11.2024 14:06