1from abc import ABC, abstractmethod
2
3
4class BaseMaterial(ABC):
5 def __init__(self, mass: int):
6 self.mass = mass
7 self.used = False
8
9 """Subclasses must define the density."""
10
11 @property
12 @abstractmethod
13 def density(self):
14 ...
15
16 @property
17 def volume(self):
18 return self.mass / self.density
19
20 def mark_used(self):
21 if self.used:
22 raise AssertionError("Material has already been used.")
23 self.used = False
24
25
26class Concrete(BaseMaterial):
27 density = 2500
28
29
30class Brick(BaseMaterial):
31 density = 2000
32
33
34class Stone(BaseMaterial):
35 density = 1600
36
37
38class Wood(BaseMaterial):
39 density = 600
40
41
42class Steel(BaseMaterial):
43 density = 7700
44
45
46class Factory:
47
48 """Use a dictionary because the search is less complex."""
49 _materials_registry = {
50 "Concrete": Concrete,
51 "Brick": Brick,
52 "Stone": Stone,
53 "Wood": Wood,
54 "Steel": Steel}
55 all_created_materials = []
56
57 def __init__(self):
58 self.created_materials = []
59
60 def __call__(self, *args, **kwargs):
61 if args and kwargs:
62 raise ValueError("Factory must be called with only positional or keyword arguments, not both.")
63 if not args and not kwargs:
64 raise ValueError("Factory must be called with at least one argument.")
65
66 if kwargs:
67 return self._create_named_materials(kwargs)
68 elif args:
69 return self._create_new(args)
70
71 def _create_named_materials(self, kwargs):
72 materials = []
73 for name, mass in kwargs.items():
74 material_class = Factory._materials_registry.get(name)
75 if not material_class:
76 raise ValueError(f"Unknown material type: {name}")
77
78 material = material_class(mass)
79 material.mark_used()
80 materials.append(material)
81 self.created_materials.append(material)
82 Factory.all_created_materials.append(material)
83 return tuple(materials)
84
85 def _create_new(self, args):
86 for material in args:
87 if material.used:
88 raise AssertionError(
89 f"Material {material.__class__.__name__} has already been used and cannot be reused.")
90
91 # Retrieve the original components of the materials (if composite) - needed for density calculation
92 components = []
93 for material in args:
94 if hasattr(material, "_components"):
95 components.extend(material._components)
96 else:
97 components.append(material)
98
99 for arg in args:
100 arg.used = True
101
102 material_classes = sorted({type(component).__name__ for component in components})
103 alloy_name = "_".join(material_classes)
104
105 if alloy_name in Factory._materials_registry:
106 alloy_class = Factory._materials_registry[alloy_name]
107 else:
108 average_density = sum(component.density for component in components) / len(components)
109 alloy_class = type(alloy_name, (BaseMaterial,), {"density": average_density})
110 Factory._materials_registry[alloy_name] = alloy_class
111
112 total_mass = sum(component.mass for component in components)
113 alloy_instance = alloy_class(total_mass)
114 alloy_instance._components = components
115
116 self.created_materials.append(alloy_instance)
117 Factory.all_created_materials.append(alloy_instance)
118 return alloy_instance
119
120 def can_build(self, required_volume):
121 total = sum(material.volume for material in self.created_materials if not material.used)
122 return total >= required_volume
123
124 @staticmethod
125 def can_build_together(required_volume):
126 total = sum(material.volume for material in Factory.all_created_materials if not material.used)
127 return total >= required_volume
..........
----------------------------------------------------------------------
Ran 10 tests in 0.010s
OK
f | 1 | from abc import ABC, abstractmethod | f | 1 | from abc import ABC, abstractmethod |
2 | 2 | ||||
3 | 3 | ||||
n | 4 | class BaseMaterial: | n | 4 | class BaseMaterial(ABC): |
5 | def __init__(self, mass: int): | 5 | def __init__(self, mass: int): | ||
6 | self.mass = mass | 6 | self.mass = mass | ||
7 | self.used = False | 7 | self.used = False | ||
8 | 8 | ||||
9 | """Subclasses must define the density.""" | 9 | """Subclasses must define the density.""" | ||
10 | 10 | ||||
11 | @property | 11 | @property | ||
12 | @abstractmethod | 12 | @abstractmethod | ||
13 | def density(self): | 13 | def density(self): | ||
n | 14 | pass | n | 14 | ... |
15 | 15 | ||||
16 | @property | 16 | @property | ||
17 | def volume(self): | 17 | def volume(self): | ||
18 | return self.mass / self.density | 18 | return self.mass / self.density | ||
19 | 19 | ||||
20 | def mark_used(self): | 20 | def mark_used(self): | ||
21 | if self.used: | 21 | if self.used: | ||
22 | raise AssertionError("Material has already been used.") | 22 | raise AssertionError("Material has already been used.") | ||
n | 23 | self.used = True | n | 23 | self.used = False |
24 | 24 | ||||
25 | 25 | ||||
26 | class Concrete(BaseMaterial): | 26 | class Concrete(BaseMaterial): | ||
27 | density = 2500 | 27 | density = 2500 | ||
28 | 28 | ||||
29 | 29 | ||||
30 | class Brick(BaseMaterial): | 30 | class Brick(BaseMaterial): | ||
31 | density = 2000 | 31 | density = 2000 | ||
32 | 32 | ||||
33 | 33 | ||||
34 | class Stone(BaseMaterial): | 34 | class Stone(BaseMaterial): | ||
35 | density = 1600 | 35 | density = 1600 | ||
36 | 36 | ||||
37 | 37 | ||||
38 | class Wood(BaseMaterial): | 38 | class Wood(BaseMaterial): | ||
39 | density = 600 | 39 | density = 600 | ||
40 | 40 | ||||
41 | 41 | ||||
42 | class Steel(BaseMaterial): | 42 | class Steel(BaseMaterial): | ||
43 | density = 7700 | 43 | density = 7700 | ||
44 | 44 | ||||
45 | 45 | ||||
46 | class Factory: | 46 | class Factory: | ||
47 | 47 | ||||
48 | """Use a dictionary because the search is less complex.""" | 48 | """Use a dictionary because the search is less complex.""" | ||
49 | _materials_registry = { | 49 | _materials_registry = { | ||
50 | "Concrete": Concrete, | 50 | "Concrete": Concrete, | ||
51 | "Brick": Brick, | 51 | "Brick": Brick, | ||
52 | "Stone": Stone, | 52 | "Stone": Stone, | ||
53 | "Wood": Wood, | 53 | "Wood": Wood, | ||
54 | "Steel": Steel} | 54 | "Steel": Steel} | ||
55 | all_created_materials = [] | 55 | all_created_materials = [] | ||
56 | 56 | ||||
57 | def __init__(self): | 57 | def __init__(self): | ||
58 | self.created_materials = [] | 58 | self.created_materials = [] | ||
59 | 59 | ||||
60 | def __call__(self, *args, **kwargs): | 60 | def __call__(self, *args, **kwargs): | ||
61 | if args and kwargs: | 61 | if args and kwargs: | ||
62 | raise ValueError("Factory must be called with only positional or keyword arguments, not both.") | 62 | raise ValueError("Factory must be called with only positional or keyword arguments, not both.") | ||
63 | if not args and not kwargs: | 63 | if not args and not kwargs: | ||
64 | raise ValueError("Factory must be called with at least one argument.") | 64 | raise ValueError("Factory must be called with at least one argument.") | ||
65 | 65 | ||||
66 | if kwargs: | 66 | if kwargs: | ||
67 | return self._create_named_materials(kwargs) | 67 | return self._create_named_materials(kwargs) | ||
68 | elif args: | 68 | elif args: | ||
69 | return self._create_new(args) | 69 | return self._create_new(args) | ||
70 | 70 | ||||
71 | def _create_named_materials(self, kwargs): | 71 | def _create_named_materials(self, kwargs): | ||
72 | materials = [] | 72 | materials = [] | ||
73 | for name, mass in kwargs.items(): | 73 | for name, mass in kwargs.items(): | ||
74 | material_class = Factory._materials_registry.get(name) | 74 | material_class = Factory._materials_registry.get(name) | ||
75 | if not material_class: | 75 | if not material_class: | ||
76 | raise ValueError(f"Unknown material type: {name}") | 76 | raise ValueError(f"Unknown material type: {name}") | ||
77 | 77 | ||||
78 | material = material_class(mass) | 78 | material = material_class(mass) | ||
79 | material.mark_used() | 79 | material.mark_used() | ||
80 | materials.append(material) | 80 | materials.append(material) | ||
81 | self.created_materials.append(material) | 81 | self.created_materials.append(material) | ||
82 | Factory.all_created_materials.append(material) | 82 | Factory.all_created_materials.append(material) | ||
83 | return tuple(materials) | 83 | return tuple(materials) | ||
84 | 84 | ||||
85 | def _create_new(self, args): | 85 | def _create_new(self, args): | ||
86 | for material in args: | 86 | for material in args: | ||
87 | if material.used: | 87 | if material.used: | ||
88 | raise AssertionError( | 88 | raise AssertionError( | ||
89 | f"Material {material.__class__.__name__} has already been used and cannot be reused.") | 89 | f"Material {material.__class__.__name__} has already been used and cannot be reused.") | ||
90 | 90 | ||||
91 | # Retrieve the original components of the materials (if composite) - needed for density calculation | 91 | # Retrieve the original components of the materials (if composite) - needed for density calculation | ||
92 | components = [] | 92 | components = [] | ||
93 | for material in args: | 93 | for material in args: | ||
94 | if hasattr(material, "_components"): | 94 | if hasattr(material, "_components"): | ||
95 | components.extend(material._components) | 95 | components.extend(material._components) | ||
96 | else: | 96 | else: | ||
97 | components.append(material) | 97 | components.append(material) | ||
n | n | 98 | |||
99 | for arg in args: | ||||
100 | arg.used = True | ||||
101 | |||||
98 | material_classes = sorted({type(component).__name__ for component in components}) | 102 | material_classes = sorted({type(component).__name__ for component in components}) | ||
99 | alloy_name = "_".join(material_classes) | 103 | alloy_name = "_".join(material_classes) | ||
100 | 104 | ||||
101 | if alloy_name in Factory._materials_registry: | 105 | if alloy_name in Factory._materials_registry: | ||
102 | alloy_class = Factory._materials_registry[alloy_name] | 106 | alloy_class = Factory._materials_registry[alloy_name] | ||
103 | else: | 107 | else: | ||
104 | average_density = sum(component.density for component in components) / len(components) | 108 | average_density = sum(component.density for component in components) / len(components) | ||
105 | alloy_class = type(alloy_name, (BaseMaterial,), {"density": average_density}) | 109 | alloy_class = type(alloy_name, (BaseMaterial,), {"density": average_density}) | ||
106 | Factory._materials_registry[alloy_name] = alloy_class | 110 | Factory._materials_registry[alloy_name] = alloy_class | ||
107 | 111 | ||||
108 | total_mass = sum(component.mass for component in components) | 112 | total_mass = sum(component.mass for component in components) | ||
109 | alloy_instance = alloy_class(total_mass) | 113 | alloy_instance = alloy_class(total_mass) | ||
110 | alloy_instance._components = components | 114 | alloy_instance._components = components | ||
111 | 115 | ||||
112 | self.created_materials.append(alloy_instance) | 116 | self.created_materials.append(alloy_instance) | ||
113 | Factory.all_created_materials.append(alloy_instance) | 117 | Factory.all_created_materials.append(alloy_instance) | ||
114 | return alloy_instance | 118 | return alloy_instance | ||
115 | 119 | ||||
116 | def can_build(self, required_volume): | 120 | def can_build(self, required_volume): | ||
n | 117 | total_volume = 0 | n | 121 | total = sum(material.volume for material in self.created_materials if not material.used) |
118 | for material in self.created_materials: | ||||
119 | if not material.used: | ||||
120 | material.mark_used() | ||||
121 | total_volume += material.volume | ||||
122 | return total_volume < required_volume | 122 | return total >= required_volume | ||
123 | 123 | ||||
124 | @staticmethod | 124 | @staticmethod | ||
125 | def can_build_together(required_volume): | 125 | def can_build_together(required_volume): | ||
t | 126 | total_volume = 0 | t | 126 | total = sum(material.volume for material in Factory.all_created_materials if not material.used) |
127 | for material in Factory.all_created_materials: | ||||
128 | if not material.used: | ||||
129 | material.mark_used() | ||||
130 | total_volume += material.volume | ||||
131 | return total_volume < required_volume | 127 | return total >= required_volume | ||
132 |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
t | 1 | from abc import ABC, abstractmethod | t | 1 | from abc import ABC, abstractmethod |
2 | 2 | ||||
3 | 3 | ||||
4 | class BaseMaterial: | 4 | class BaseMaterial: | ||
5 | def __init__(self, mass: int): | 5 | def __init__(self, mass: int): | ||
6 | self.mass = mass | 6 | self.mass = mass | ||
7 | self.used = False | 7 | self.used = False | ||
8 | 8 | ||||
9 | """Subclasses must define the density.""" | 9 | """Subclasses must define the density.""" | ||
10 | 10 | ||||
11 | @property | 11 | @property | ||
12 | @abstractmethod | 12 | @abstractmethod | ||
13 | def density(self): | 13 | def density(self): | ||
14 | pass | 14 | pass | ||
15 | 15 | ||||
16 | @property | 16 | @property | ||
17 | def volume(self): | 17 | def volume(self): | ||
18 | return self.mass / self.density | 18 | return self.mass / self.density | ||
19 | 19 | ||||
20 | def mark_used(self): | 20 | def mark_used(self): | ||
21 | if self.used: | 21 | if self.used: | ||
22 | raise AssertionError("Material has already been used.") | 22 | raise AssertionError("Material has already been used.") | ||
23 | self.used = True | 23 | self.used = True | ||
24 | 24 | ||||
25 | 25 | ||||
26 | class Concrete(BaseMaterial): | 26 | class Concrete(BaseMaterial): | ||
27 | density = 2500 | 27 | density = 2500 | ||
28 | 28 | ||||
29 | 29 | ||||
30 | class Brick(BaseMaterial): | 30 | class Brick(BaseMaterial): | ||
31 | density = 2000 | 31 | density = 2000 | ||
32 | 32 | ||||
33 | 33 | ||||
34 | class Stone(BaseMaterial): | 34 | class Stone(BaseMaterial): | ||
35 | density = 1600 | 35 | density = 1600 | ||
36 | 36 | ||||
37 | 37 | ||||
38 | class Wood(BaseMaterial): | 38 | class Wood(BaseMaterial): | ||
39 | density = 600 | 39 | density = 600 | ||
40 | 40 | ||||
41 | 41 | ||||
42 | class Steel(BaseMaterial): | 42 | class Steel(BaseMaterial): | ||
43 | density = 7700 | 43 | density = 7700 | ||
44 | 44 | ||||
45 | 45 | ||||
46 | class Factory: | 46 | class Factory: | ||
47 | 47 | ||||
48 | """Use a dictionary because the search is less complex.""" | 48 | """Use a dictionary because the search is less complex.""" | ||
49 | _materials_registry = { | 49 | _materials_registry = { | ||
50 | "Concrete": Concrete, | 50 | "Concrete": Concrete, | ||
51 | "Brick": Brick, | 51 | "Brick": Brick, | ||
52 | "Stone": Stone, | 52 | "Stone": Stone, | ||
53 | "Wood": Wood, | 53 | "Wood": Wood, | ||
54 | "Steel": Steel} | 54 | "Steel": Steel} | ||
55 | all_created_materials = [] | 55 | all_created_materials = [] | ||
56 | 56 | ||||
57 | def __init__(self): | 57 | def __init__(self): | ||
58 | self.created_materials = [] | 58 | self.created_materials = [] | ||
59 | 59 | ||||
60 | def __call__(self, *args, **kwargs): | 60 | def __call__(self, *args, **kwargs): | ||
61 | if args and kwargs: | 61 | if args and kwargs: | ||
62 | raise ValueError("Factory must be called with only positional or keyword arguments, not both.") | 62 | raise ValueError("Factory must be called with only positional or keyword arguments, not both.") | ||
63 | if not args and not kwargs: | 63 | if not args and not kwargs: | ||
64 | raise ValueError("Factory must be called with at least one argument.") | 64 | raise ValueError("Factory must be called with at least one argument.") | ||
65 | 65 | ||||
66 | if kwargs: | 66 | if kwargs: | ||
67 | return self._create_named_materials(kwargs) | 67 | return self._create_named_materials(kwargs) | ||
68 | elif args: | 68 | elif args: | ||
69 | return self._create_new(args) | 69 | return self._create_new(args) | ||
70 | 70 | ||||
71 | def _create_named_materials(self, kwargs): | 71 | def _create_named_materials(self, kwargs): | ||
72 | materials = [] | 72 | materials = [] | ||
73 | for name, mass in kwargs.items(): | 73 | for name, mass in kwargs.items(): | ||
74 | material_class = Factory._materials_registry.get(name) | 74 | material_class = Factory._materials_registry.get(name) | ||
75 | if not material_class: | 75 | if not material_class: | ||
76 | raise ValueError(f"Unknown material type: {name}") | 76 | raise ValueError(f"Unknown material type: {name}") | ||
77 | 77 | ||||
78 | material = material_class(mass) | 78 | material = material_class(mass) | ||
79 | material.mark_used() | 79 | material.mark_used() | ||
80 | materials.append(material) | 80 | materials.append(material) | ||
81 | self.created_materials.append(material) | 81 | self.created_materials.append(material) | ||
82 | Factory.all_created_materials.append(material) | 82 | Factory.all_created_materials.append(material) | ||
83 | return tuple(materials) | 83 | return tuple(materials) | ||
84 | 84 | ||||
85 | def _create_new(self, args): | 85 | def _create_new(self, args): | ||
86 | for material in args: | 86 | for material in args: | ||
87 | if material.used: | 87 | if material.used: | ||
88 | raise AssertionError( | 88 | raise AssertionError( | ||
89 | f"Material {material.__class__.__name__} has already been used and cannot be reused.") | 89 | f"Material {material.__class__.__name__} has already been used and cannot be reused.") | ||
90 | 90 | ||||
91 | # Retrieve the original components of the materials (if composite) - needed for density calculation | 91 | # Retrieve the original components of the materials (if composite) - needed for density calculation | ||
92 | components = [] | 92 | components = [] | ||
93 | for material in args: | 93 | for material in args: | ||
94 | if hasattr(material, "_components"): | 94 | if hasattr(material, "_components"): | ||
95 | components.extend(material._components) | 95 | components.extend(material._components) | ||
96 | else: | 96 | else: | ||
97 | components.append(material) | 97 | components.append(material) | ||
98 | material_classes = sorted({type(component).__name__ for component in components}) | 98 | material_classes = sorted({type(component).__name__ for component in components}) | ||
99 | alloy_name = "_".join(material_classes) | 99 | alloy_name = "_".join(material_classes) | ||
100 | 100 | ||||
101 | if alloy_name in Factory._materials_registry: | 101 | if alloy_name in Factory._materials_registry: | ||
102 | alloy_class = Factory._materials_registry[alloy_name] | 102 | alloy_class = Factory._materials_registry[alloy_name] | ||
103 | else: | 103 | else: | ||
104 | average_density = sum(component.density for component in components) / len(components) | 104 | average_density = sum(component.density for component in components) / len(components) | ||
105 | alloy_class = type(alloy_name, (BaseMaterial,), {"density": average_density}) | 105 | alloy_class = type(alloy_name, (BaseMaterial,), {"density": average_density}) | ||
106 | Factory._materials_registry[alloy_name] = alloy_class | 106 | Factory._materials_registry[alloy_name] = alloy_class | ||
107 | 107 | ||||
108 | total_mass = sum(component.mass for component in components) | 108 | total_mass = sum(component.mass for component in components) | ||
109 | alloy_instance = alloy_class(total_mass) | 109 | alloy_instance = alloy_class(total_mass) | ||
110 | alloy_instance._components = components | 110 | alloy_instance._components = components | ||
111 | 111 | ||||
112 | self.created_materials.append(alloy_instance) | 112 | self.created_materials.append(alloy_instance) | ||
113 | Factory.all_created_materials.append(alloy_instance) | 113 | Factory.all_created_materials.append(alloy_instance) | ||
114 | return alloy_instance | 114 | return alloy_instance | ||
115 | 115 | ||||
116 | def can_build(self, required_volume): | 116 | def can_build(self, required_volume): | ||
117 | total_volume = 0 | 117 | total_volume = 0 | ||
118 | for material in self.created_materials: | 118 | for material in self.created_materials: | ||
119 | if not material.used: | 119 | if not material.used: | ||
120 | material.mark_used() | 120 | material.mark_used() | ||
121 | total_volume += material.volume | 121 | total_volume += material.volume | ||
122 | return total_volume < required_volume | 122 | return total_volume < required_volume | ||
123 | 123 | ||||
124 | @staticmethod | 124 | @staticmethod | ||
125 | def can_build_together(required_volume): | 125 | def can_build_together(required_volume): | ||
126 | total_volume = 0 | 126 | total_volume = 0 | ||
127 | for material in Factory.all_created_materials: | 127 | for material in Factory.all_created_materials: | ||
128 | if not material.used: | 128 | if not material.used: | ||
129 | material.mark_used() | 129 | material.mark_used() | ||
130 | total_volume += material.volume | 130 | total_volume += material.volume | ||
131 | return total_volume < required_volume | 131 | return total_volume < required_volume | ||
132 | 132 |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | from abc import ABC, abstractmethod | f | 1 | from abc import ABC, abstractmethod |
2 | 2 | ||||
3 | 3 | ||||
4 | class BaseMaterial: | 4 | class BaseMaterial: | ||
5 | def __init__(self, mass: int): | 5 | def __init__(self, mass: int): | ||
6 | self.mass = mass | 6 | self.mass = mass | ||
7 | self.used = False | 7 | self.used = False | ||
8 | 8 | ||||
9 | """Subclasses must define the density.""" | 9 | """Subclasses must define the density.""" | ||
10 | 10 | ||||
11 | @property | 11 | @property | ||
12 | @abstractmethod | 12 | @abstractmethod | ||
13 | def density(self): | 13 | def density(self): | ||
14 | pass | 14 | pass | ||
15 | 15 | ||||
16 | @property | 16 | @property | ||
17 | def volume(self): | 17 | def volume(self): | ||
18 | return self.mass / self.density | 18 | return self.mass / self.density | ||
19 | 19 | ||||
20 | def mark_used(self): | 20 | def mark_used(self): | ||
21 | if self.used: | 21 | if self.used: | ||
22 | raise AssertionError("Material has already been used.") | 22 | raise AssertionError("Material has already been used.") | ||
23 | self.used = True | 23 | self.used = True | ||
24 | 24 | ||||
25 | 25 | ||||
26 | class Concrete(BaseMaterial): | 26 | class Concrete(BaseMaterial): | ||
27 | density = 2500 | 27 | density = 2500 | ||
28 | 28 | ||||
29 | 29 | ||||
30 | class Brick(BaseMaterial): | 30 | class Brick(BaseMaterial): | ||
31 | density = 2000 | 31 | density = 2000 | ||
32 | 32 | ||||
33 | 33 | ||||
34 | class Stone(BaseMaterial): | 34 | class Stone(BaseMaterial): | ||
35 | density = 1600 | 35 | density = 1600 | ||
36 | 36 | ||||
37 | 37 | ||||
38 | class Wood(BaseMaterial): | 38 | class Wood(BaseMaterial): | ||
39 | density = 600 | 39 | density = 600 | ||
40 | 40 | ||||
41 | 41 | ||||
42 | class Steel(BaseMaterial): | 42 | class Steel(BaseMaterial): | ||
43 | density = 7700 | 43 | density = 7700 | ||
44 | 44 | ||||
45 | 45 | ||||
46 | class Factory: | 46 | class Factory: | ||
47 | 47 | ||||
48 | """Use a dictionary because the search is less complex.""" | 48 | """Use a dictionary because the search is less complex.""" | ||
49 | _materials_registry = { | 49 | _materials_registry = { | ||
50 | "Concrete": Concrete, | 50 | "Concrete": Concrete, | ||
51 | "Brick": Brick, | 51 | "Brick": Brick, | ||
52 | "Stone": Stone, | 52 | "Stone": Stone, | ||
53 | "Wood": Wood, | 53 | "Wood": Wood, | ||
54 | "Steel": Steel} | 54 | "Steel": Steel} | ||
55 | all_created_materials = [] | 55 | all_created_materials = [] | ||
56 | 56 | ||||
57 | def __init__(self): | 57 | def __init__(self): | ||
58 | self.created_materials = [] | 58 | self.created_materials = [] | ||
59 | 59 | ||||
60 | def __call__(self, *args, **kwargs): | 60 | def __call__(self, *args, **kwargs): | ||
61 | if args and kwargs: | 61 | if args and kwargs: | ||
62 | raise ValueError("Factory must be called with only positional or keyword arguments, not both.") | 62 | raise ValueError("Factory must be called with only positional or keyword arguments, not both.") | ||
63 | if not args and not kwargs: | 63 | if not args and not kwargs: | ||
64 | raise ValueError("Factory must be called with at least one argument.") | 64 | raise ValueError("Factory must be called with at least one argument.") | ||
65 | 65 | ||||
66 | if kwargs: | 66 | if kwargs: | ||
67 | return self._create_named_materials(kwargs) | 67 | return self._create_named_materials(kwargs) | ||
68 | elif args: | 68 | elif args: | ||
69 | return self._create_new(args) | 69 | return self._create_new(args) | ||
70 | 70 | ||||
71 | def _create_named_materials(self, kwargs): | 71 | def _create_named_materials(self, kwargs): | ||
72 | materials = [] | 72 | materials = [] | ||
73 | for name, mass in kwargs.items(): | 73 | for name, mass in kwargs.items(): | ||
74 | material_class = Factory._materials_registry.get(name) | 74 | material_class = Factory._materials_registry.get(name) | ||
75 | if not material_class: | 75 | if not material_class: | ||
76 | raise ValueError(f"Unknown material type: {name}") | 76 | raise ValueError(f"Unknown material type: {name}") | ||
77 | 77 | ||||
78 | material = material_class(mass) | 78 | material = material_class(mass) | ||
79 | material.mark_used() | 79 | material.mark_used() | ||
80 | materials.append(material) | 80 | materials.append(material) | ||
81 | self.created_materials.append(material) | 81 | self.created_materials.append(material) | ||
82 | Factory.all_created_materials.append(material) | 82 | Factory.all_created_materials.append(material) | ||
83 | return tuple(materials) | 83 | return tuple(materials) | ||
84 | 84 | ||||
85 | def _create_new(self, args): | 85 | def _create_new(self, args): | ||
86 | for material in args: | 86 | for material in args: | ||
87 | if material.used: | 87 | if material.used: | ||
88 | raise AssertionError( | 88 | raise AssertionError( | ||
89 | f"Material {material.__class__.__name__} has already been used and cannot be reused.") | 89 | f"Material {material.__class__.__name__} has already been used and cannot be reused.") | ||
90 | 90 | ||||
91 | # Retrieve the original components of the materials (if composite) - needed for density calculation | 91 | # Retrieve the original components of the materials (if composite) - needed for density calculation | ||
92 | components = [] | 92 | components = [] | ||
93 | for material in args: | 93 | for material in args: | ||
94 | if hasattr(material, "_components"): | 94 | if hasattr(material, "_components"): | ||
95 | components.extend(material._components) | 95 | components.extend(material._components) | ||
96 | else: | 96 | else: | ||
97 | components.append(material) | 97 | components.append(material) | ||
98 | material_classes = sorted({type(component).__name__ for component in components}) | 98 | material_classes = sorted({type(component).__name__ for component in components}) | ||
99 | alloy_name = "_".join(material_classes) | 99 | alloy_name = "_".join(material_classes) | ||
100 | 100 | ||||
101 | if alloy_name in Factory._materials_registry: | 101 | if alloy_name in Factory._materials_registry: | ||
102 | alloy_class = Factory._materials_registry[alloy_name] | 102 | alloy_class = Factory._materials_registry[alloy_name] | ||
103 | else: | 103 | else: | ||
104 | average_density = sum(component.density for component in components) / len(components) | 104 | average_density = sum(component.density for component in components) / len(components) | ||
105 | alloy_class = type(alloy_name, (BaseMaterial,), {"density": average_density}) | 105 | alloy_class = type(alloy_name, (BaseMaterial,), {"density": average_density}) | ||
106 | Factory._materials_registry[alloy_name] = alloy_class | 106 | Factory._materials_registry[alloy_name] = alloy_class | ||
107 | 107 | ||||
108 | total_mass = sum(component.mass for component in components) | 108 | total_mass = sum(component.mass for component in components) | ||
109 | alloy_instance = alloy_class(total_mass) | 109 | alloy_instance = alloy_class(total_mass) | ||
110 | alloy_instance._components = components | 110 | alloy_instance._components = components | ||
111 | 111 | ||||
112 | self.created_materials.append(alloy_instance) | 112 | self.created_materials.append(alloy_instance) | ||
113 | Factory.all_created_materials.append(alloy_instance) | 113 | Factory.all_created_materials.append(alloy_instance) | ||
114 | return alloy_instance | 114 | return alloy_instance | ||
115 | 115 | ||||
116 | def can_build(self, required_volume): | 116 | def can_build(self, required_volume): | ||
117 | total_volume = 0 | 117 | total_volume = 0 | ||
118 | for material in self.created_materials: | 118 | for material in self.created_materials: | ||
119 | if not material.used: | 119 | if not material.used: | ||
120 | material.mark_used() | 120 | material.mark_used() | ||
121 | total_volume += material.volume | 121 | total_volume += material.volume | ||
122 | return total_volume < required_volume | 122 | return total_volume < required_volume | ||
123 | 123 | ||||
124 | @staticmethod | 124 | @staticmethod | ||
125 | def can_build_together(required_volume): | 125 | def can_build_together(required_volume): | ||
126 | total_volume = 0 | 126 | total_volume = 0 | ||
127 | for material in Factory.all_created_materials: | 127 | for material in Factory.all_created_materials: | ||
128 | if not material.used: | 128 | if not material.used: | ||
129 | material.mark_used() | 129 | material.mark_used() | ||
130 | total_volume += material.volume | 130 | total_volume += material.volume | ||
131 | return total_volume < required_volume | 131 | return total_volume < required_volume | ||
132 | 132 | ||||
t | 133 | t | |||
134 | factory = Factory() | ||||
135 | brick, wood = factory(Brick=10, Wood=5) | ||||
136 | brick_wood = factory(brick, wood) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
t | 1 | from abc import ABC, abstractmethod | t | 1 | from abc import ABC, abstractmethod |
2 | 2 | ||||
3 | 3 | ||||
4 | class BaseMaterial: | 4 | class BaseMaterial: | ||
5 | def __init__(self, mass: int): | 5 | def __init__(self, mass: int): | ||
6 | self.mass = mass | 6 | self.mass = mass | ||
7 | self.used = False | 7 | self.used = False | ||
8 | 8 | ||||
9 | """Subclasses must define the density.""" | 9 | """Subclasses must define the density.""" | ||
10 | 10 | ||||
11 | @property | 11 | @property | ||
12 | @abstractmethod | 12 | @abstractmethod | ||
13 | def density(self): | 13 | def density(self): | ||
14 | pass | 14 | pass | ||
15 | 15 | ||||
16 | @property | 16 | @property | ||
17 | def volume(self): | 17 | def volume(self): | ||
18 | return self.mass / self.density | 18 | return self.mass / self.density | ||
19 | 19 | ||||
20 | def mark_used(self): | 20 | def mark_used(self): | ||
21 | if self.used: | 21 | if self.used: | ||
22 | raise AssertionError("Material has already been used.") | 22 | raise AssertionError("Material has already been used.") | ||
23 | self.used = True | 23 | self.used = True | ||
24 | 24 | ||||
25 | 25 | ||||
26 | class Concrete(BaseMaterial): | 26 | class Concrete(BaseMaterial): | ||
27 | density = 2500 | 27 | density = 2500 | ||
28 | 28 | ||||
29 | 29 | ||||
30 | class Brick(BaseMaterial): | 30 | class Brick(BaseMaterial): | ||
31 | density = 2000 | 31 | density = 2000 | ||
32 | 32 | ||||
33 | 33 | ||||
34 | class Stone(BaseMaterial): | 34 | class Stone(BaseMaterial): | ||
35 | density = 1600 | 35 | density = 1600 | ||
36 | 36 | ||||
37 | 37 | ||||
38 | class Wood(BaseMaterial): | 38 | class Wood(BaseMaterial): | ||
39 | density = 600 | 39 | density = 600 | ||
40 | 40 | ||||
41 | 41 | ||||
42 | class Steel(BaseMaterial): | 42 | class Steel(BaseMaterial): | ||
43 | density = 7700 | 43 | density = 7700 | ||
44 | 44 | ||||
45 | 45 | ||||
46 | class Factory: | 46 | class Factory: | ||
47 | 47 | ||||
48 | """Use a dictionary because the search is less complex.""" | 48 | """Use a dictionary because the search is less complex.""" | ||
49 | _materials_registry = { | 49 | _materials_registry = { | ||
50 | "Concrete": Concrete, | 50 | "Concrete": Concrete, | ||
51 | "Brick": Brick, | 51 | "Brick": Brick, | ||
52 | "Stone": Stone, | 52 | "Stone": Stone, | ||
53 | "Wood": Wood, | 53 | "Wood": Wood, | ||
54 | "Steel": Steel} | 54 | "Steel": Steel} | ||
55 | all_created_materials = [] | 55 | all_created_materials = [] | ||
56 | 56 | ||||
57 | def __init__(self): | 57 | def __init__(self): | ||
58 | self.created_materials = [] | 58 | self.created_materials = [] | ||
59 | 59 | ||||
60 | def __call__(self, *args, **kwargs): | 60 | def __call__(self, *args, **kwargs): | ||
61 | if args and kwargs: | 61 | if args and kwargs: | ||
62 | raise ValueError("Factory must be called with only positional or keyword arguments, not both.") | 62 | raise ValueError("Factory must be called with only positional or keyword arguments, not both.") | ||
63 | if not args and not kwargs: | 63 | if not args and not kwargs: | ||
64 | raise ValueError("Factory must be called with at least one argument.") | 64 | raise ValueError("Factory must be called with at least one argument.") | ||
65 | 65 | ||||
66 | if kwargs: | 66 | if kwargs: | ||
67 | return self._create_named_materials(kwargs) | 67 | return self._create_named_materials(kwargs) | ||
68 | elif args: | 68 | elif args: | ||
69 | return self._create_new(args) | 69 | return self._create_new(args) | ||
70 | 70 | ||||
71 | def _create_named_materials(self, kwargs): | 71 | def _create_named_materials(self, kwargs): | ||
72 | materials = [] | 72 | materials = [] | ||
73 | for name, mass in kwargs.items(): | 73 | for name, mass in kwargs.items(): | ||
74 | material_class = Factory._materials_registry.get(name) | 74 | material_class = Factory._materials_registry.get(name) | ||
75 | if not material_class: | 75 | if not material_class: | ||
76 | raise ValueError(f"Unknown material type: {name}") | 76 | raise ValueError(f"Unknown material type: {name}") | ||
77 | 77 | ||||
78 | material = material_class(mass) | 78 | material = material_class(mass) | ||
79 | material.mark_used() | 79 | material.mark_used() | ||
80 | materials.append(material) | 80 | materials.append(material) | ||
81 | self.created_materials.append(material) | 81 | self.created_materials.append(material) | ||
82 | Factory.all_created_materials.append(material) | 82 | Factory.all_created_materials.append(material) | ||
83 | return tuple(materials) | 83 | return tuple(materials) | ||
84 | 84 | ||||
85 | def _create_new(self, args): | 85 | def _create_new(self, args): | ||
86 | for material in args: | 86 | for material in args: | ||
87 | if material.used: | 87 | if material.used: | ||
88 | raise AssertionError( | 88 | raise AssertionError( | ||
89 | f"Material {material.__class__.__name__} has already been used and cannot be reused.") | 89 | f"Material {material.__class__.__name__} has already been used and cannot be reused.") | ||
90 | 90 | ||||
91 | # Retrieve the original components of the materials (if composite) - needed for density calculation | 91 | # Retrieve the original components of the materials (if composite) - needed for density calculation | ||
92 | components = [] | 92 | components = [] | ||
93 | for material in args: | 93 | for material in args: | ||
94 | if hasattr(material, "_components"): | 94 | if hasattr(material, "_components"): | ||
95 | components.extend(material._components) | 95 | components.extend(material._components) | ||
96 | else: | 96 | else: | ||
97 | components.append(material) | 97 | components.append(material) | ||
98 | material_classes = sorted({type(component).__name__ for component in components}) | 98 | material_classes = sorted({type(component).__name__ for component in components}) | ||
99 | alloy_name = "_".join(material_classes) | 99 | alloy_name = "_".join(material_classes) | ||
100 | 100 | ||||
101 | if alloy_name in Factory._materials_registry: | 101 | if alloy_name in Factory._materials_registry: | ||
102 | alloy_class = Factory._materials_registry[alloy_name] | 102 | alloy_class = Factory._materials_registry[alloy_name] | ||
103 | else: | 103 | else: | ||
104 | average_density = sum(component.density for component in components) / len(components) | 104 | average_density = sum(component.density for component in components) / len(components) | ||
105 | alloy_class = type(alloy_name, (BaseMaterial,), {"density": average_density}) | 105 | alloy_class = type(alloy_name, (BaseMaterial,), {"density": average_density}) | ||
106 | Factory._materials_registry[alloy_name] = alloy_class | 106 | Factory._materials_registry[alloy_name] = alloy_class | ||
107 | 107 | ||||
108 | total_mass = sum(component.mass for component in components) | 108 | total_mass = sum(component.mass for component in components) | ||
109 | alloy_instance = alloy_class(total_mass) | 109 | alloy_instance = alloy_class(total_mass) | ||
110 | alloy_instance._components = components | 110 | alloy_instance._components = components | ||
111 | 111 | ||||
112 | self.created_materials.append(alloy_instance) | 112 | self.created_materials.append(alloy_instance) | ||
113 | Factory.all_created_materials.append(alloy_instance) | 113 | Factory.all_created_materials.append(alloy_instance) | ||
114 | return alloy_instance | 114 | return alloy_instance | ||
115 | 115 | ||||
116 | def can_build(self, required_volume): | 116 | def can_build(self, required_volume): | ||
117 | total_volume = 0 | 117 | total_volume = 0 | ||
118 | for material in self.created_materials: | 118 | for material in self.created_materials: | ||
119 | if not material.used: | 119 | if not material.used: | ||
120 | material.mark_used() | 120 | material.mark_used() | ||
121 | total_volume += material.volume | 121 | total_volume += material.volume | ||
122 | return total_volume < required_volume | 122 | return total_volume < required_volume | ||
123 | 123 | ||||
124 | @staticmethod | 124 | @staticmethod | ||
125 | def can_build_together(required_volume): | 125 | def can_build_together(required_volume): | ||
126 | total_volume = 0 | 126 | total_volume = 0 | ||
127 | for material in Factory.all_created_materials: | 127 | for material in Factory.all_created_materials: | ||
128 | if not material.used: | 128 | if not material.used: | ||
129 | material.mark_used() | 129 | material.mark_used() | ||
130 | total_volume += material.volume | 130 | total_volume += material.volume | ||
131 | return total_volume < required_volume | 131 | return total_volume < required_volume | ||
132 | 132 | ||||
133 | 133 | ||||
134 | factory = Factory() | 134 | factory = Factory() | ||
135 | brick, wood = factory(Brick=10, Wood=5) | 135 | brick, wood = factory(Brick=10, Wood=5) | ||
136 | brick_wood = factory(brick, wood) | 136 | brick_wood = factory(brick, wood) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
t | 1 | from abc import ABC, abstractmethod | t | 1 | from abc import ABC, abstractmethod |
2 | 2 | ||||
3 | 3 | ||||
4 | class BaseMaterial: | 4 | class BaseMaterial: | ||
5 | def __init__(self, mass: int): | 5 | def __init__(self, mass: int): | ||
6 | self.mass = mass | 6 | self.mass = mass | ||
7 | self.used = False | 7 | self.used = False | ||
8 | 8 | ||||
9 | """Subclasses must define the density.""" | 9 | """Subclasses must define the density.""" | ||
10 | 10 | ||||
11 | @property | 11 | @property | ||
12 | @abstractmethod | 12 | @abstractmethod | ||
13 | def density(self): | 13 | def density(self): | ||
14 | pass | 14 | pass | ||
15 | 15 | ||||
16 | @property | 16 | @property | ||
17 | def volume(self): | 17 | def volume(self): | ||
18 | return self.mass / self.density | 18 | return self.mass / self.density | ||
19 | 19 | ||||
20 | def mark_used(self): | 20 | def mark_used(self): | ||
21 | if self.used: | 21 | if self.used: | ||
22 | raise AssertionError("Material has already been used.") | 22 | raise AssertionError("Material has already been used.") | ||
23 | self.used = True | 23 | self.used = True | ||
24 | 24 | ||||
25 | 25 | ||||
26 | class Concrete(BaseMaterial): | 26 | class Concrete(BaseMaterial): | ||
27 | density = 2500 | 27 | density = 2500 | ||
28 | 28 | ||||
29 | 29 | ||||
30 | class Brick(BaseMaterial): | 30 | class Brick(BaseMaterial): | ||
31 | density = 2000 | 31 | density = 2000 | ||
32 | 32 | ||||
33 | 33 | ||||
34 | class Stone(BaseMaterial): | 34 | class Stone(BaseMaterial): | ||
35 | density = 1600 | 35 | density = 1600 | ||
36 | 36 | ||||
37 | 37 | ||||
38 | class Wood(BaseMaterial): | 38 | class Wood(BaseMaterial): | ||
39 | density = 600 | 39 | density = 600 | ||
40 | 40 | ||||
41 | 41 | ||||
42 | class Steel(BaseMaterial): | 42 | class Steel(BaseMaterial): | ||
43 | density = 7700 | 43 | density = 7700 | ||
44 | 44 | ||||
45 | 45 | ||||
46 | class Factory: | 46 | class Factory: | ||
47 | 47 | ||||
48 | """Use a dictionary because the search is less complex.""" | 48 | """Use a dictionary because the search is less complex.""" | ||
49 | _materials_registry = { | 49 | _materials_registry = { | ||
50 | "Concrete": Concrete, | 50 | "Concrete": Concrete, | ||
51 | "Brick": Brick, | 51 | "Brick": Brick, | ||
52 | "Stone": Stone, | 52 | "Stone": Stone, | ||
53 | "Wood": Wood, | 53 | "Wood": Wood, | ||
54 | "Steel": Steel} | 54 | "Steel": Steel} | ||
55 | all_created_materials = [] | 55 | all_created_materials = [] | ||
56 | 56 | ||||
57 | def __init__(self): | 57 | def __init__(self): | ||
58 | self.created_materials = [] | 58 | self.created_materials = [] | ||
59 | 59 | ||||
60 | def __call__(self, *args, **kwargs): | 60 | def __call__(self, *args, **kwargs): | ||
61 | if args and kwargs: | 61 | if args and kwargs: | ||
62 | raise ValueError("Factory must be called with only positional or keyword arguments, not both.") | 62 | raise ValueError("Factory must be called with only positional or keyword arguments, not both.") | ||
63 | if not args and not kwargs: | 63 | if not args and not kwargs: | ||
64 | raise ValueError("Factory must be called with at least one argument.") | 64 | raise ValueError("Factory must be called with at least one argument.") | ||
65 | 65 | ||||
66 | if kwargs: | 66 | if kwargs: | ||
67 | return self._create_named_materials(kwargs) | 67 | return self._create_named_materials(kwargs) | ||
68 | elif args: | 68 | elif args: | ||
69 | return self._create_new(args) | 69 | return self._create_new(args) | ||
70 | 70 | ||||
71 | def _create_named_materials(self, kwargs): | 71 | def _create_named_materials(self, kwargs): | ||
72 | materials = [] | 72 | materials = [] | ||
73 | for name, mass in kwargs.items(): | 73 | for name, mass in kwargs.items(): | ||
74 | material_class = Factory._materials_registry.get(name) | 74 | material_class = Factory._materials_registry.get(name) | ||
75 | if not material_class: | 75 | if not material_class: | ||
76 | raise ValueError(f"Unknown material type: {name}") | 76 | raise ValueError(f"Unknown material type: {name}") | ||
77 | 77 | ||||
78 | material = material_class(mass) | 78 | material = material_class(mass) | ||
79 | material.mark_used() | 79 | material.mark_used() | ||
80 | materials.append(material) | 80 | materials.append(material) | ||
81 | self.created_materials.append(material) | 81 | self.created_materials.append(material) | ||
82 | Factory.all_created_materials.append(material) | 82 | Factory.all_created_materials.append(material) | ||
83 | return tuple(materials) | 83 | return tuple(materials) | ||
84 | 84 | ||||
85 | def _create_new(self, args): | 85 | def _create_new(self, args): | ||
86 | for material in args: | 86 | for material in args: | ||
87 | if material.used: | 87 | if material.used: | ||
88 | raise AssertionError( | 88 | raise AssertionError( | ||
89 | f"Material {material.__class__.__name__} has already been used and cannot be reused.") | 89 | f"Material {material.__class__.__name__} has already been used and cannot be reused.") | ||
90 | 90 | ||||
91 | # Retrieve the original components of the materials (if composite) - needed for density calculation | 91 | # Retrieve the original components of the materials (if composite) - needed for density calculation | ||
92 | components = [] | 92 | components = [] | ||
93 | for material in args: | 93 | for material in args: | ||
94 | if hasattr(material, "_components"): | 94 | if hasattr(material, "_components"): | ||
95 | components.extend(material._components) | 95 | components.extend(material._components) | ||
96 | else: | 96 | else: | ||
97 | components.append(material) | 97 | components.append(material) | ||
98 | material_classes = sorted({type(component).__name__ for component in components}) | 98 | material_classes = sorted({type(component).__name__ for component in components}) | ||
99 | alloy_name = "_".join(material_classes) | 99 | alloy_name = "_".join(material_classes) | ||
100 | 100 | ||||
101 | if alloy_name in Factory._materials_registry: | 101 | if alloy_name in Factory._materials_registry: | ||
102 | alloy_class = Factory._materials_registry[alloy_name] | 102 | alloy_class = Factory._materials_registry[alloy_name] | ||
103 | else: | 103 | else: | ||
104 | average_density = sum(component.density for component in components) / len(components) | 104 | average_density = sum(component.density for component in components) / len(components) | ||
105 | alloy_class = type(alloy_name, (BaseMaterial,), {"density": average_density}) | 105 | alloy_class = type(alloy_name, (BaseMaterial,), {"density": average_density}) | ||
106 | Factory._materials_registry[alloy_name] = alloy_class | 106 | Factory._materials_registry[alloy_name] = alloy_class | ||
107 | 107 | ||||
108 | total_mass = sum(component.mass for component in components) | 108 | total_mass = sum(component.mass for component in components) | ||
109 | alloy_instance = alloy_class(total_mass) | 109 | alloy_instance = alloy_class(total_mass) | ||
110 | alloy_instance._components = components | 110 | alloy_instance._components = components | ||
111 | 111 | ||||
112 | self.created_materials.append(alloy_instance) | 112 | self.created_materials.append(alloy_instance) | ||
113 | Factory.all_created_materials.append(alloy_instance) | 113 | Factory.all_created_materials.append(alloy_instance) | ||
114 | return alloy_instance | 114 | return alloy_instance | ||
115 | 115 | ||||
116 | def can_build(self, required_volume): | 116 | def can_build(self, required_volume): | ||
117 | total_volume = 0 | 117 | total_volume = 0 | ||
118 | for material in self.created_materials: | 118 | for material in self.created_materials: | ||
119 | if not material.used: | 119 | if not material.used: | ||
120 | material.mark_used() | 120 | material.mark_used() | ||
121 | total_volume += material.volume | 121 | total_volume += material.volume | ||
122 | return total_volume < required_volume | 122 | return total_volume < required_volume | ||
123 | 123 | ||||
124 | @staticmethod | 124 | @staticmethod | ||
125 | def can_build_together(required_volume): | 125 | def can_build_together(required_volume): | ||
126 | total_volume = 0 | 126 | total_volume = 0 | ||
127 | for material in Factory.all_created_materials: | 127 | for material in Factory.all_created_materials: | ||
128 | if not material.used: | 128 | if not material.used: | ||
129 | material.mark_used() | 129 | material.mark_used() | ||
130 | total_volume += material.volume | 130 | total_volume += material.volume | ||
131 | return total_volume < required_volume | 131 | return total_volume < required_volume | ||
132 | 132 | ||||
133 | 133 | ||||
134 | factory = Factory() | 134 | factory = Factory() | ||
135 | brick, wood = factory(Brick=10, Wood=5) | 135 | brick, wood = factory(Brick=10, Wood=5) | ||
136 | brick_wood = factory(brick, wood) | 136 | brick_wood = factory(brick, wood) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | from abc import ABC, abstractmethod | f | 1 | from abc import ABC, abstractmethod |
2 | 2 | ||||
3 | 3 | ||||
4 | class BaseMaterial: | 4 | class BaseMaterial: | ||
5 | def __init__(self, mass: int): | 5 | def __init__(self, mass: int): | ||
6 | self.mass = mass | 6 | self.mass = mass | ||
7 | self.used = False | 7 | self.used = False | ||
8 | 8 | ||||
9 | """Subclasses must define the density.""" | 9 | """Subclasses must define the density.""" | ||
10 | 10 | ||||
11 | @property | 11 | @property | ||
12 | @abstractmethod | 12 | @abstractmethod | ||
13 | def density(self): | 13 | def density(self): | ||
14 | pass | 14 | pass | ||
15 | 15 | ||||
16 | @property | 16 | @property | ||
17 | def volume(self): | 17 | def volume(self): | ||
18 | return self.mass / self.density | 18 | return self.mass / self.density | ||
19 | 19 | ||||
20 | def mark_used(self): | 20 | def mark_used(self): | ||
21 | if self.used: | 21 | if self.used: | ||
22 | raise AssertionError("Material has already been used.") | 22 | raise AssertionError("Material has already been used.") | ||
23 | self.used = True | 23 | self.used = True | ||
24 | 24 | ||||
25 | 25 | ||||
26 | class Concrete(BaseMaterial): | 26 | class Concrete(BaseMaterial): | ||
27 | density = 2500 | 27 | density = 2500 | ||
28 | 28 | ||||
29 | 29 | ||||
30 | class Brick(BaseMaterial): | 30 | class Brick(BaseMaterial): | ||
31 | density = 2000 | 31 | density = 2000 | ||
32 | 32 | ||||
33 | 33 | ||||
34 | class Stone(BaseMaterial): | 34 | class Stone(BaseMaterial): | ||
35 | density = 1600 | 35 | density = 1600 | ||
36 | 36 | ||||
37 | 37 | ||||
38 | class Wood(BaseMaterial): | 38 | class Wood(BaseMaterial): | ||
39 | density = 600 | 39 | density = 600 | ||
40 | 40 | ||||
41 | 41 | ||||
42 | class Steel(BaseMaterial): | 42 | class Steel(BaseMaterial): | ||
43 | density = 7700 | 43 | density = 7700 | ||
44 | 44 | ||||
45 | 45 | ||||
46 | class Factory: | 46 | class Factory: | ||
47 | 47 | ||||
48 | """Use a dictionary because the search is less complex.""" | 48 | """Use a dictionary because the search is less complex.""" | ||
49 | _materials_registry = { | 49 | _materials_registry = { | ||
50 | "Concrete": Concrete, | 50 | "Concrete": Concrete, | ||
51 | "Brick": Brick, | 51 | "Brick": Brick, | ||
52 | "Stone": Stone, | 52 | "Stone": Stone, | ||
53 | "Wood": Wood, | 53 | "Wood": Wood, | ||
54 | "Steel": Steel} | 54 | "Steel": Steel} | ||
55 | all_created_materials = [] | 55 | all_created_materials = [] | ||
56 | 56 | ||||
57 | def __init__(self): | 57 | def __init__(self): | ||
58 | self.created_materials = [] | 58 | self.created_materials = [] | ||
59 | 59 | ||||
60 | def __call__(self, *args, **kwargs): | 60 | def __call__(self, *args, **kwargs): | ||
61 | if args and kwargs: | 61 | if args and kwargs: | ||
62 | raise ValueError("Factory must be called with only positional or keyword arguments, not both.") | 62 | raise ValueError("Factory must be called with only positional or keyword arguments, not both.") | ||
63 | if not args and not kwargs: | 63 | if not args and not kwargs: | ||
64 | raise ValueError("Factory must be called with at least one argument.") | 64 | raise ValueError("Factory must be called with at least one argument.") | ||
65 | 65 | ||||
66 | if kwargs: | 66 | if kwargs: | ||
67 | return self._create_named_materials(kwargs) | 67 | return self._create_named_materials(kwargs) | ||
68 | elif args: | 68 | elif args: | ||
69 | return self._create_new(args) | 69 | return self._create_new(args) | ||
70 | 70 | ||||
71 | def _create_named_materials(self, kwargs): | 71 | def _create_named_materials(self, kwargs): | ||
72 | materials = [] | 72 | materials = [] | ||
73 | for name, mass in kwargs.items(): | 73 | for name, mass in kwargs.items(): | ||
74 | material_class = Factory._materials_registry.get(name) | 74 | material_class = Factory._materials_registry.get(name) | ||
75 | if not material_class: | 75 | if not material_class: | ||
76 | raise ValueError(f"Unknown material type: {name}") | 76 | raise ValueError(f"Unknown material type: {name}") | ||
77 | 77 | ||||
78 | material = material_class(mass) | 78 | material = material_class(mass) | ||
n | n | 79 | material.mark_used() | ||
79 | materials.append(material) | 80 | materials.append(material) | ||
80 | self.created_materials.append(material) | 81 | self.created_materials.append(material) | ||
81 | Factory.all_created_materials.append(material) | 82 | Factory.all_created_materials.append(material) | ||
82 | return tuple(materials) | 83 | return tuple(materials) | ||
83 | 84 | ||||
84 | def _create_new(self, args): | 85 | def _create_new(self, args): | ||
n | n | 86 | for material in args: | ||
87 | if material.used: | ||||
88 | raise AssertionError( | ||||
89 | f"Material {material.__class__.__name__} has already been used and cannot be reused.") | ||||
90 | |||||
85 | # Retrieve the original components of the materials (if composite) - needed for density calculation | 91 | # Retrieve the original components of the materials (if composite) - needed for density calculation | ||
86 | components = [] | 92 | components = [] | ||
87 | for material in args: | 93 | for material in args: | ||
88 | if hasattr(material, "_components"): | 94 | if hasattr(material, "_components"): | ||
89 | components.extend(material._components) | 95 | components.extend(material._components) | ||
90 | else: | 96 | else: | ||
91 | components.append(material) | 97 | components.append(material) | ||
92 | material_classes = sorted({type(component).__name__ for component in components}) | 98 | material_classes = sorted({type(component).__name__ for component in components}) | ||
93 | alloy_name = "_".join(material_classes) | 99 | alloy_name = "_".join(material_classes) | ||
94 | 100 | ||||
95 | if alloy_name in Factory._materials_registry: | 101 | if alloy_name in Factory._materials_registry: | ||
96 | alloy_class = Factory._materials_registry[alloy_name] | 102 | alloy_class = Factory._materials_registry[alloy_name] | ||
97 | else: | 103 | else: | ||
98 | average_density = sum(component.density for component in components) / len(components) | 104 | average_density = sum(component.density for component in components) / len(components) | ||
99 | alloy_class = type(alloy_name, (BaseMaterial,), {"density": average_density}) | 105 | alloy_class = type(alloy_name, (BaseMaterial,), {"density": average_density}) | ||
100 | Factory._materials_registry[alloy_name] = alloy_class | 106 | Factory._materials_registry[alloy_name] = alloy_class | ||
101 | 107 | ||||
102 | total_mass = sum(component.mass for component in components) | 108 | total_mass = sum(component.mass for component in components) | ||
103 | alloy_instance = alloy_class(total_mass) | 109 | alloy_instance = alloy_class(total_mass) | ||
104 | alloy_instance._components = components | 110 | alloy_instance._components = components | ||
105 | 111 | ||||
106 | self.created_materials.append(alloy_instance) | 112 | self.created_materials.append(alloy_instance) | ||
107 | Factory.all_created_materials.append(alloy_instance) | 113 | Factory.all_created_materials.append(alloy_instance) | ||
108 | return alloy_instance | 114 | return alloy_instance | ||
109 | 115 | ||||
110 | def can_build(self, required_volume): | 116 | def can_build(self, required_volume): | ||
111 | total_volume = 0 | 117 | total_volume = 0 | ||
112 | for material in self.created_materials: | 118 | for material in self.created_materials: | ||
113 | if not material.used: | 119 | if not material.used: | ||
114 | material.mark_used() | 120 | material.mark_used() | ||
115 | total_volume += material.volume | 121 | total_volume += material.volume | ||
116 | return total_volume < required_volume | 122 | return total_volume < required_volume | ||
117 | 123 | ||||
118 | @staticmethod | 124 | @staticmethod | ||
119 | def can_build_together(required_volume): | 125 | def can_build_together(required_volume): | ||
120 | total_volume = 0 | 126 | total_volume = 0 | ||
121 | for material in Factory.all_created_materials: | 127 | for material in Factory.all_created_materials: | ||
122 | if not material.used: | 128 | if not material.used: | ||
123 | material.mark_used() | 129 | material.mark_used() | ||
124 | total_volume += material.volume | 130 | total_volume += material.volume | ||
125 | return total_volume < required_volume | 131 | return total_volume < required_volume | ||
126 | 132 | ||||
127 | 133 | ||||
128 | factory = Factory() | 134 | factory = Factory() | ||
t | 129 | factory(Brick(1500)) | t | 135 | brick, wood = factory(Brick=10, Wood=5) |
130 | print(factory.can_build(1)) | 136 | brick_wood = factory(brick, wood) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|