1class Material:
2 material_classes = {}
3 material_densities = {}
4 density = None
5
6 def __init__(self, mass):
7 self.mass = mass
8 self.valid = True
9 self._factory = None
10
11 @property
12 def volume(self):
13 return self.mass / self.density
14
15 def get_base_material_names(self):
16 return {self.__class__.__name__}
17
18
19class Concrete(Material):
20 density = 2500
21
22
23class Brick(Material):
24 density = 2000
25
26
27class Stone(Material):
28 density = 1600
29
30
31class Wood(Material):
32 density = 600
33
34
35class Steel(Material):
36 density = 7700
37
38
39for mat_class in [Concrete, Brick, Stone, Wood, Steel]:
40 Material.material_classes[mat_class.__name__] = mat_class
41 Material.material_densities[mat_class.__name__] = mat_class.density
42
43
44class Factory:
45 composite_classes = {}
46 all_valid_materials = []
47
48 def __init__(self):
49 self.materials = [] # materials created by this factory
50
51 def __call__(self, *args, **kwargs):
52 if (args and kwargs) or (not args and not kwargs):
53 raise ValueError("Factory must be called with either positional or keyword arguments, not both or none")
54
55 if args: # Create a composite material
56 for arg in args:
57 assert isinstance(arg, Material), "All positional arguments must be Material instances"
58
59 for arg in args:
60 assert arg.valid, "Material has already been used"
61
62 base_material_names = set()
63 for arg in args:
64 base_material_names.update(arg.get_base_material_names())
65
66 composite_class_name = '_'.join(sorted(base_material_names))
67
68 base_material_densities = [Material.material_densities[name] for name in base_material_names]
69 density = sum(base_material_densities) / len(base_material_densities)
70
71 key = frozenset(base_material_names) # Use frozenset for hashability
72 if key in Factory.composite_classes: # Existing composite class
73 composite_class = Factory.composite_classes[key]
74 else: # Create a new composite class
75 composite_class = type(composite_class_name, (Material,), {'density': density})
76 composite_class.base_material_names = base_material_names
77
78 def get_base_material_names(self):
79 return self.__class__.base_material_names
80
81 composite_class.get_base_material_names = get_base_material_names
82
83 Factory.composite_classes[key] = composite_class
84
85 Material.material_classes[composite_class_name] = composite_class
86 Material.material_densities[composite_class_name] = density
87
88 total_mass = sum(arg.mass for arg in args)
89 composite_instance = composite_class(total_mass)
90
91 for arg in args:
92 arg.valid = False
93
94 composite_instance._factory = self
95 composite_instance.valid = True
96
97 self.materials.append(composite_instance)
98 Factory.all_valid_materials.append(composite_instance)
99
100 return composite_instance
101 else: # Create individual materials
102 materials = []
103 for name, mass in kwargs.items():
104 if name in Material.material_classes:
105 material_class = Material.material_classes[name]
106 else:
107 raise ValueError(f"Unknown material {name}")
108
109 instance = material_class(mass)
110 instance.valid = True
111 instance._factory = self
112
113 materials.append(instance)
114 self.materials.append(instance)
115 Factory.all_valid_materials.append(instance)
116
117 return tuple(materials)
118
119 def can_build(self, volume):
120 total_volume = sum(m.volume for m in self.materials if m.valid)
121 return total_volume >= volume
122
123 @classmethod
124 def can_build_together(cls, volume):
125 total_volume = sum(m.volume for m in cls.all_valid_materials if m.valid)
126 return total_volume >= volume
.......F..
======================================================================
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.014s
FAILED (failures=1)
26.11.2024 13:34
26.11.2024 13:29
26.11.2024 13:30
26.11.2024 13:31