1MATERIAL_BITS = {
2 'Concrete': 1 << 0, # 1 (binary 00001)
3 'Brick': 1 << 1, # 2 (binary 00010)
4 'Stone': 1 << 2, # 4 (binary 00100)
5 'Wood': 1 << 3, # 8 (binary 01000)
6 'Steel': 1 << 4, # 16 (binary 10000)
7}
8
9BASE_MATERIAL_DENSITIES = {
10 'Concrete': 2500,
11 'Brick': 2000,
12 'Stone': 1600,
13 'Wood': 600,
14 'Steel': 7700,
15}
16
17
18class Material:
19 def __init__(self, mass: float):
20 if mass <= 0:
21 raise ValueError("Mass must be a positive number.")
22 self.mass: float = mass
23 self.used: bool = False
24 self._volume: float = None # Cached value for the volume
25
26 def __hash__(self):
27 return id(self) # Hashing the object by its unique id
28
29 @property
30 def volume(self):
31 if self._volume is None:
32 self._volume = self.mass / self.density
33 return self._volume
34
35
36class Concrete(Material):
37 density = BASE_MATERIAL_DENSITIES['Concrete']
38 material_bit = MATERIAL_BITS['Concrete']
39
40
41class Brick(Material):
42 density = BASE_MATERIAL_DENSITIES['Brick']
43 material_bit = MATERIAL_BITS['Brick']
44
45
46class Stone(Material):
47 density = BASE_MATERIAL_DENSITIES['Stone']
48 material_bit = MATERIAL_BITS['Stone']
49
50
51class Wood(Material):
52 density = BASE_MATERIAL_DENSITIES['Wood']
53 material_bit = MATERIAL_BITS['Wood']
54
55
56class Steel(Material):
57 density = BASE_MATERIAL_DENSITIES['Steel']
58 material_bit = MATERIAL_BITS['Steel']
59
60
61class Factory:
62 material_classes = {
63 'Concrete': Concrete,
64 'Brick': Brick,
65 'Stone': Stone,
66 'Wood': Wood,
67 'Steel': Steel,
68 }
69 dynamic_classes = {}
70 all_materials = set()
71
72 # Precompute bitmask to material names mapping for quick lookup
73 _bitmask_to_materials = {
74 bitmask: [name for name, bit in MATERIAL_BITS.items() if bitmask & bit]
75 for bitmask in range(1, 1 << len(MATERIAL_BITS))
76 }
77
78 def __init__(self):
79 self.material_bitmask = 0 # Represent current materials as a bitmask
80 self.materials = set() # Set of Material instances managed by this factory
81
82 def __call__(self, *args, **kwargs):
83 self._validate_call(args, kwargs)
84
85 if kwargs:
86 return self._create_materials(kwargs)
87 else:
88 return self._use_materials(args)
89
90 def _validate_call(self, args, kwargs):
91 if (args and kwargs) or (not args and not kwargs):
92 raise ValueError("Invalid input.")
93
94 def _create_materials(self, kwargs):
95 materials_created = []
96 for material_name, mass in kwargs.items():
97 material_class = self._get_material_class(material_name)
98 material = self._instantiate_material(material_class, mass)
99 self._add_material(material)
100 materials_created.append(material)
101 self.material_bitmask |= material_class.material_bit # Use material_class.material_bit
102 return tuple(materials_created)
103
104 def _get_material_class(self, material_name):
105 material_class = self.material_classes.get(material_name)
106 if material_class is None:
107 raise ValueError(f"Unknown material: {material_name}")
108 return material_class
109
110 def _instantiate_material(self, material_class, mass):
111 return material_class(mass)
112
113 def _add_material(self, material):
114 self.materials.add(material) # Add to factory's materials
115 Factory.all_materials.add(material) # Add to global all_materials
116 # Update the bitmask with the material's bit
117 self.material_bitmask |= material.material_bit
118
119 def _use_materials(self, materials):
120 self._validate_materials(materials)
121 self._mark_materials_as_used(materials)
122
123 material_bitmask, total_mass = self._calculate_bitmask_and_mass(materials)
124 new_class = self._get_or_create_dynamic_class(material_bitmask)
125
126 new_material = self._create_new_material(new_class, total_mass)
127 self.material_bitmask |= new_class.material_bit # Set the bit for the new composite material
128 return new_material
129
130 def _validate_materials(self, materials):
131 known_classes = tuple(self.material_classes.values()) + tuple(self.dynamic_classes.values())
132
133 for material in materials:
134 if not isinstance(material, known_classes):
135 raise ValueError(f"Invalid material instance: {material}")
136 if material.used:
137 raise AssertionError(f"Material {material} is already used.")
138
139 def _mark_materials_as_used(self, materials):
140 for material in materials:
141 material.used = True
142 self.materials.discard(material) # Remove from factory's materials
143 self.material_bitmask &= ~material.material_bit # Clear the bit for the used material
144 Factory.all_materials.discard(material) # Remove from global all_materials
145
146 def _calculate_bitmask_and_mass(self, materials):
147 material_bitmask = 0
148 total_mass = 0
149 for material in materials:
150 material_bitmask |= material.material_bit # Combine material bits using bitwise OR
151 total_mass += material.mass
152 return material_bitmask, total_mass
153
154 def _get_or_create_dynamic_class(self, material_bitmask):
155 # Try to find existing class by material_bitmask
156 for cls in self.dynamic_classes.values():
157 if cls.material_bit == material_bitmask:
158 return cls
159 # Generate class name
160 base_materials = self._determine_base_materials(material_bitmask)
161 class_name = self._generate_class_name(base_materials)
162 # Check if class already exists in material_classes
163 if class_name in self.material_classes:
164 return self.material_classes[class_name]
165 # Create new dynamic class
166 return self._create_dynamic_class(material_bitmask)
167
168 def _create_dynamic_class(self, material_bitmask):
169 base_materials = self._determine_base_materials(material_bitmask)
170 density = self._calculate_density(base_materials)
171 class_name = self._generate_class_name(base_materials)
172
173 attributes = {
174 'density': density,
175 'material_bit': material_bitmask,
176 }
177 new_class = type(class_name, (Material,), attributes)
178 # Store the new class using the class name as the key
179 self.material_classes[class_name] = new_class
180 self.dynamic_classes[material_bitmask] = new_class
181 return new_class
182
183 def _determine_base_materials(self, material_bitmask):
184 # Use precomputed bitmask to materials mapping for efficiency
185 return self._bitmask_to_materials.get(material_bitmask, [])
186
187 def _calculate_density(self, base_materials):
188 densities = [BASE_MATERIAL_DENSITIES[name] for name in base_materials]
189 return sum(densities) / len(densities)
190
191 def _generate_class_name(self, base_materials):
192 # Generate class name by sorting material names to ensure consistency
193 return '_'.join(sorted(base_materials))
194
195 def _create_new_material(self, new_class, total_mass):
196 new_material = new_class(total_mass)
197 self.materials.add(new_material) # Add to factory's materials
198 Factory.all_materials.add(new_material) # Add to global all_materials
199 return new_material
200
201 def can_build(self, volume_needed):
202 # Calculate total volume of current materials in this factory
203 total_volume = sum(material.volume for material in self.materials)
204 return total_volume >= volume_needed
205
206 @classmethod
207 def can_build_together(cls, volume_needed):
208 # Calculate total volume of all materials across all factories
209 total_volume = sum(material.volume for material in cls.all_materials)
210 return total_volume >= volume_needed
..........
----------------------------------------------------------------------
Ran 10 tests in 0.014s
OK
Виктор Бечев
23.11.2024 16:21Разбито изглежда значително по-добре!
Що се отнася до речника с битовата маска - побитовите операции са доста бързи и би следвало да можеш да се оправиш само с тях, без дедикиран речник, но не пречи.
|
Йоан Байчев
22.11.2024 16:47Относно новата версия на кода: създавам речник, който свързва всяка възможно битова маска със списък от материали, които съставляват тази маска. Това позволява бързо идентифициране на материалите при работа с комбинации. Мотивацията ми за това е по-бързо търсене и лесна подръжка, ако се добавят нови материали, просто се разширява речникът MATERIAL_BITS и новите комбинации автоматично се включват в _bitmask_to_materials , без необходимост от допълнителни промени в логиката на кода. Също добавих инициализиране на self.material_bitmask = 0 в класа Factory, което задава дефоултно състояние на фабриката без налични материали.
|
Виктор Бечев
21.11.2024 14:09Четейки си мисля друго - при положение, че използваш битовете, трябва ли ти наистина `base_materials`? На практика изразяват едно и също, просто побитовата репрезентация го прави с единици за участващ материал, а `base_materials` е просто human-readable вариант.
Можеш да минеш без едно от двете и така и така си го направил яко с битовете - why not remove `base_materials`?
|
Виктор Бечев
21.11.2024 14:07Битовата репрезентация е доста cool и умно решение!
|
Йоан Байчев
20.11.2024 23:56Кеширам обема в Material като това би било особено полезно в сценарии, където свойството volume се достъпва многократно. Чрез битовата репрезентация симулирам уникално идентифициране като също избягвам конфликти на имена, когато комбинирам два материала използвам логическо или "|", което се обработва доста по-бързо. Чрез логическото и "&" можем бързо да определим кои материали са включени в дадена комбинация.
|
| f | 1 | MATERIAL_BITS = { | f | 1 | MATERIAL_BITS = { |
| 2 | 'Concrete': 1 << 0, # 1 (binary 00001) | 2 | 'Concrete': 1 << 0, # 1 (binary 00001) | ||
| 3 | 'Brick': 1 << 1, # 2 (binary 00010) | 3 | 'Brick': 1 << 1, # 2 (binary 00010) | ||
| 4 | 'Stone': 1 << 2, # 4 (binary 00100) | 4 | 'Stone': 1 << 2, # 4 (binary 00100) | ||
| 5 | 'Wood': 1 << 3, # 8 (binary 01000) | 5 | 'Wood': 1 << 3, # 8 (binary 01000) | ||
| 6 | 'Steel': 1 << 4, # 16 (binary 10000) | 6 | 'Steel': 1 << 4, # 16 (binary 10000) | ||
| 7 | } | 7 | } | ||
| 8 | 8 | ||||
| 9 | BASE_MATERIAL_DENSITIES = { | 9 | BASE_MATERIAL_DENSITIES = { | ||
| 10 | 'Concrete': 2500, | 10 | 'Concrete': 2500, | ||
| 11 | 'Brick': 2000, | 11 | 'Brick': 2000, | ||
| 12 | 'Stone': 1600, | 12 | 'Stone': 1600, | ||
| 13 | 'Wood': 600, | 13 | 'Wood': 600, | ||
| 14 | 'Steel': 7700, | 14 | 'Steel': 7700, | ||
| 15 | } | 15 | } | ||
| 16 | 16 | ||||
| 17 | 17 | ||||
| 18 | class Material: | 18 | class Material: | ||
| 19 | def __init__(self, mass: float): | 19 | def __init__(self, mass: float): | ||
| 20 | if mass <= 0: | 20 | if mass <= 0: | ||
| 21 | raise ValueError("Mass must be a positive number.") | 21 | raise ValueError("Mass must be a positive number.") | ||
| 22 | self.mass: float = mass | 22 | self.mass: float = mass | ||
| 23 | self.used: bool = False | 23 | self.used: bool = False | ||
| 24 | self._volume: float = None # Cached value for the volume | 24 | self._volume: float = None # Cached value for the volume | ||
| 25 | 25 | ||||
| 26 | def __hash__(self): | 26 | def __hash__(self): | ||
| 27 | return id(self) # Hashing the object by its unique id | 27 | return id(self) # Hashing the object by its unique id | ||
| 28 | 28 | ||||
| 29 | @property | 29 | @property | ||
| 30 | def volume(self): | 30 | def volume(self): | ||
| 31 | if self._volume is None: | 31 | if self._volume is None: | ||
| 32 | self._volume = self.mass / self.density | 32 | self._volume = self.mass / self.density | ||
| 33 | return self._volume | 33 | return self._volume | ||
| 34 | 34 | ||||
| 35 | 35 | ||||
| 36 | class Concrete(Material): | 36 | class Concrete(Material): | ||
| 37 | density = BASE_MATERIAL_DENSITIES['Concrete'] | 37 | density = BASE_MATERIAL_DENSITIES['Concrete'] | ||
| 38 | material_bit = MATERIAL_BITS['Concrete'] | 38 | material_bit = MATERIAL_BITS['Concrete'] | ||
| 39 | 39 | ||||
| 40 | 40 | ||||
| 41 | class Brick(Material): | 41 | class Brick(Material): | ||
| 42 | density = BASE_MATERIAL_DENSITIES['Brick'] | 42 | density = BASE_MATERIAL_DENSITIES['Brick'] | ||
| 43 | material_bit = MATERIAL_BITS['Brick'] | 43 | material_bit = MATERIAL_BITS['Brick'] | ||
| 44 | 44 | ||||
| 45 | 45 | ||||
| 46 | class Stone(Material): | 46 | class Stone(Material): | ||
| 47 | density = BASE_MATERIAL_DENSITIES['Stone'] | 47 | density = BASE_MATERIAL_DENSITIES['Stone'] | ||
| 48 | material_bit = MATERIAL_BITS['Stone'] | 48 | material_bit = MATERIAL_BITS['Stone'] | ||
| 49 | 49 | ||||
| 50 | 50 | ||||
| 51 | class Wood(Material): | 51 | class Wood(Material): | ||
| 52 | density = BASE_MATERIAL_DENSITIES['Wood'] | 52 | density = BASE_MATERIAL_DENSITIES['Wood'] | ||
| 53 | material_bit = MATERIAL_BITS['Wood'] | 53 | material_bit = MATERIAL_BITS['Wood'] | ||
| 54 | 54 | ||||
| 55 | 55 | ||||
| 56 | class Steel(Material): | 56 | class Steel(Material): | ||
| 57 | density = BASE_MATERIAL_DENSITIES['Steel'] | 57 | density = BASE_MATERIAL_DENSITIES['Steel'] | ||
| 58 | material_bit = MATERIAL_BITS['Steel'] | 58 | material_bit = MATERIAL_BITS['Steel'] | ||
| 59 | 59 | ||||
| 60 | 60 | ||||
| 61 | class Factory: | 61 | class Factory: | ||
| 62 | material_classes = { | 62 | material_classes = { | ||
| 63 | 'Concrete': Concrete, | 63 | 'Concrete': Concrete, | ||
| 64 | 'Brick': Brick, | 64 | 'Brick': Brick, | ||
| 65 | 'Stone': Stone, | 65 | 'Stone': Stone, | ||
| 66 | 'Wood': Wood, | 66 | 'Wood': Wood, | ||
| 67 | 'Steel': Steel, | 67 | 'Steel': Steel, | ||
| 68 | } | 68 | } | ||
| 69 | dynamic_classes = {} | 69 | dynamic_classes = {} | ||
| 70 | all_materials = set() | 70 | all_materials = set() | ||
| 71 | 71 | ||||
| 72 | # Precompute bitmask to material names mapping for quick lookup | 72 | # Precompute bitmask to material names mapping for quick lookup | ||
| 73 | _bitmask_to_materials = { | 73 | _bitmask_to_materials = { | ||
| 74 | bitmask: [name for name, bit in MATERIAL_BITS.items() if bitmask & bit] | 74 | bitmask: [name for name, bit in MATERIAL_BITS.items() if bitmask & bit] | ||
| 75 | for bitmask in range(1, 1 << len(MATERIAL_BITS)) | 75 | for bitmask in range(1, 1 << len(MATERIAL_BITS)) | ||
| 76 | } | 76 | } | ||
| 77 | 77 | ||||
| 78 | def __init__(self): | 78 | def __init__(self): | ||
| 79 | self.material_bitmask = 0 # Represent current materials as a bitmask | 79 | self.material_bitmask = 0 # Represent current materials as a bitmask | ||
| 80 | self.materials = set() # Set of Material instances managed by this factory | 80 | self.materials = set() # Set of Material instances managed by this factory | ||
| 81 | 81 | ||||
| 82 | def __call__(self, *args, **kwargs): | 82 | def __call__(self, *args, **kwargs): | ||
| 83 | self._validate_call(args, kwargs) | 83 | self._validate_call(args, kwargs) | ||
| 84 | 84 | ||||
| 85 | if kwargs: | 85 | if kwargs: | ||
| 86 | return self._create_materials(kwargs) | 86 | return self._create_materials(kwargs) | ||
| 87 | else: | 87 | else: | ||
| 88 | return self._use_materials(args) | 88 | return self._use_materials(args) | ||
| 89 | 89 | ||||
| 90 | def _validate_call(self, args, kwargs): | 90 | def _validate_call(self, args, kwargs): | ||
| 91 | if (args and kwargs) or (not args and not kwargs): | 91 | if (args and kwargs) or (not args and not kwargs): | ||
| 92 | raise ValueError("Invalid input.") | 92 | raise ValueError("Invalid input.") | ||
| 93 | 93 | ||||
| 94 | def _create_materials(self, kwargs): | 94 | def _create_materials(self, kwargs): | ||
| 95 | materials_created = [] | 95 | materials_created = [] | ||
| 96 | for material_name, mass in kwargs.items(): | 96 | for material_name, mass in kwargs.items(): | ||
| 97 | material_class = self._get_material_class(material_name) | 97 | material_class = self._get_material_class(material_name) | ||
| 98 | material = self._instantiate_material(material_class, mass) | 98 | material = self._instantiate_material(material_class, mass) | ||
| 99 | self._add_material(material) | 99 | self._add_material(material) | ||
| 100 | materials_created.append(material) | 100 | materials_created.append(material) | ||
| n | 101 | self.material_bitmask |= MATERIAL_BITS[material_name] # Set the bit for the created material | n | 101 | self.material_bitmask |= material_class.material_bit # Use material_class.material_bit |
| 102 | return tuple(materials_created) | 102 | return tuple(materials_created) | ||
| 103 | 103 | ||||
| 104 | def _get_material_class(self, material_name): | 104 | def _get_material_class(self, material_name): | ||
| n | 105 | material_class = self.material_classes.get(material_name) or self.dynamic_classes.get(material_name) | n | 105 | material_class = self.material_classes.get(material_name) |
| 106 | if material_class is None: | 106 | if material_class is None: | ||
| 107 | raise ValueError(f"Unknown material: {material_name}") | 107 | raise ValueError(f"Unknown material: {material_name}") | ||
| 108 | return material_class | 108 | return material_class | ||
| 109 | 109 | ||||
| 110 | def _instantiate_material(self, material_class, mass): | 110 | def _instantiate_material(self, material_class, mass): | ||
| 111 | return material_class(mass) | 111 | return material_class(mass) | ||
| 112 | 112 | ||||
| 113 | def _add_material(self, material): | 113 | def _add_material(self, material): | ||
| 114 | self.materials.add(material) # Add to factory's materials | 114 | self.materials.add(material) # Add to factory's materials | ||
| 115 | Factory.all_materials.add(material) # Add to global all_materials | 115 | Factory.all_materials.add(material) # Add to global all_materials | ||
| 116 | # Update the bitmask with the material's bit | 116 | # Update the bitmask with the material's bit | ||
| n | 117 | self.material_bitmask |= material.material_bit # Already done in _create_materials | n | 117 | self.material_bitmask |= material.material_bit |
| 118 | 118 | ||||
| 119 | def _use_materials(self, materials): | 119 | def _use_materials(self, materials): | ||
| 120 | self._validate_materials(materials) | 120 | self._validate_materials(materials) | ||
| 121 | self._mark_materials_as_used(materials) | 121 | self._mark_materials_as_used(materials) | ||
| 122 | 122 | ||||
| 123 | material_bitmask, total_mass = self._calculate_bitmask_and_mass(materials) | 123 | material_bitmask, total_mass = self._calculate_bitmask_and_mass(materials) | ||
| 124 | new_class = self._get_or_create_dynamic_class(material_bitmask) | 124 | new_class = self._get_or_create_dynamic_class(material_bitmask) | ||
| 125 | 125 | ||||
| 126 | new_material = self._create_new_material(new_class, total_mass) | 126 | new_material = self._create_new_material(new_class, total_mass) | ||
| 127 | self.material_bitmask |= new_class.material_bit # Set the bit for the new composite material | 127 | self.material_bitmask |= new_class.material_bit # Set the bit for the new composite material | ||
| 128 | return new_material | 128 | return new_material | ||
| 129 | 129 | ||||
| 130 | def _validate_materials(self, materials): | 130 | def _validate_materials(self, materials): | ||
| 131 | known_classes = tuple(self.material_classes.values()) + tuple(self.dynamic_classes.values()) | 131 | known_classes = tuple(self.material_classes.values()) + tuple(self.dynamic_classes.values()) | ||
| 132 | 132 | ||||
| 133 | for material in materials: | 133 | for material in materials: | ||
| 134 | if not isinstance(material, known_classes): | 134 | if not isinstance(material, known_classes): | ||
| 135 | raise ValueError(f"Invalid material instance: {material}") | 135 | raise ValueError(f"Invalid material instance: {material}") | ||
| 136 | if material.used: | 136 | if material.used: | ||
| 137 | raise AssertionError(f"Material {material} is already used.") | 137 | raise AssertionError(f"Material {material} is already used.") | ||
| 138 | 138 | ||||
| 139 | def _mark_materials_as_used(self, materials): | 139 | def _mark_materials_as_used(self, materials): | ||
| 140 | for material in materials: | 140 | for material in materials: | ||
| 141 | material.used = True | 141 | material.used = True | ||
| 142 | self.materials.discard(material) # Remove from factory's materials | 142 | self.materials.discard(material) # Remove from factory's materials | ||
| 143 | self.material_bitmask &= ~material.material_bit # Clear the bit for the used material | 143 | self.material_bitmask &= ~material.material_bit # Clear the bit for the used material | ||
| 144 | Factory.all_materials.discard(material) # Remove from global all_materials | 144 | Factory.all_materials.discard(material) # Remove from global all_materials | ||
| 145 | 145 | ||||
| 146 | def _calculate_bitmask_and_mass(self, materials): | 146 | def _calculate_bitmask_and_mass(self, materials): | ||
| 147 | material_bitmask = 0 | 147 | material_bitmask = 0 | ||
| 148 | total_mass = 0 | 148 | total_mass = 0 | ||
| 149 | for material in materials: | 149 | for material in materials: | ||
| 150 | material_bitmask |= material.material_bit # Combine material bits using bitwise OR | 150 | material_bitmask |= material.material_bit # Combine material bits using bitwise OR | ||
| 151 | total_mass += material.mass | 151 | total_mass += material.mass | ||
| 152 | return material_bitmask, total_mass | 152 | return material_bitmask, total_mass | ||
| 153 | 153 | ||||
| 154 | def _get_or_create_dynamic_class(self, material_bitmask): | 154 | def _get_or_create_dynamic_class(self, material_bitmask): | ||
| n | 155 | if material_bitmask in self.dynamic_classes: | n | 155 | # Try to find existing class by material_bitmask |
| 156 | return self.dynamic_classes[material_bitmask] | 156 | for cls in self.dynamic_classes.values(): | ||
| 157 | else: | 157 | if cls.material_bit == material_bitmask: | ||
| 158 | return cls | ||||
| 159 | # Generate class name | ||||
| 160 | base_materials = self._determine_base_materials(material_bitmask) | ||||
| 161 | class_name = self._generate_class_name(base_materials) | ||||
| 162 | # Check if class already exists in material_classes | ||||
| 163 | if class_name in self.material_classes: | ||||
| 164 | return self.material_classes[class_name] | ||||
| 165 | # Create new dynamic class | ||||
| 158 | return self._create_dynamic_class(material_bitmask) | 166 | return self._create_dynamic_class(material_bitmask) | ||
| 159 | 167 | ||||
| 160 | def _create_dynamic_class(self, material_bitmask): | 168 | def _create_dynamic_class(self, material_bitmask): | ||
| 161 | base_materials = self._determine_base_materials(material_bitmask) | 169 | base_materials = self._determine_base_materials(material_bitmask) | ||
| 162 | density = self._calculate_density(base_materials) | 170 | density = self._calculate_density(base_materials) | ||
| 163 | class_name = self._generate_class_name(base_materials) | 171 | class_name = self._generate_class_name(base_materials) | ||
| 164 | 172 | ||||
| 165 | attributes = { | 173 | attributes = { | ||
| 166 | 'density': density, | 174 | 'density': density, | ||
| 167 | 'material_bit': material_bitmask, | 175 | 'material_bit': material_bitmask, | ||
| 168 | } | 176 | } | ||
| 169 | new_class = type(class_name, (Material,), attributes) | 177 | new_class = type(class_name, (Material,), attributes) | ||
| t | t | 178 | # Store the new class using the class name as the key | ||
| 179 | self.material_classes[class_name] = new_class | ||||
| 170 | self.dynamic_classes[material_bitmask] = new_class | 180 | self.dynamic_classes[material_bitmask] = new_class | ||
| 171 | return new_class | 181 | return new_class | ||
| 172 | 182 | ||||
| 173 | def _determine_base_materials(self, material_bitmask): | 183 | def _determine_base_materials(self, material_bitmask): | ||
| 174 | # Use precomputed bitmask to materials mapping for efficiency | 184 | # Use precomputed bitmask to materials mapping for efficiency | ||
| 175 | return self._bitmask_to_materials.get(material_bitmask, []) | 185 | return self._bitmask_to_materials.get(material_bitmask, []) | ||
| 176 | 186 | ||||
| 177 | def _calculate_density(self, base_materials): | 187 | def _calculate_density(self, base_materials): | ||
| 178 | densities = [BASE_MATERIAL_DENSITIES[name] for name in base_materials] | 188 | densities = [BASE_MATERIAL_DENSITIES[name] for name in base_materials] | ||
| 179 | return sum(densities) / len(densities) | 189 | return sum(densities) / len(densities) | ||
| 180 | 190 | ||||
| 181 | def _generate_class_name(self, base_materials): | 191 | def _generate_class_name(self, base_materials): | ||
| 182 | # Generate class name by sorting material names to ensure consistency | 192 | # Generate class name by sorting material names to ensure consistency | ||
| 183 | return '_'.join(sorted(base_materials)) | 193 | return '_'.join(sorted(base_materials)) | ||
| 184 | 194 | ||||
| 185 | def _create_new_material(self, new_class, total_mass): | 195 | def _create_new_material(self, new_class, total_mass): | ||
| 186 | new_material = new_class(total_mass) | 196 | new_material = new_class(total_mass) | ||
| 187 | self.materials.add(new_material) # Add to factory's materials | 197 | self.materials.add(new_material) # Add to factory's materials | ||
| 188 | Factory.all_materials.add(new_material) # Add to global all_materials | 198 | Factory.all_materials.add(new_material) # Add to global all_materials | ||
| 189 | return new_material | 199 | return new_material | ||
| 190 | 200 | ||||
| 191 | def can_build(self, volume_needed): | 201 | def can_build(self, volume_needed): | ||
| 192 | # Calculate total volume of current materials in this factory | 202 | # Calculate total volume of current materials in this factory | ||
| 193 | total_volume = sum(material.volume for material in self.materials) | 203 | total_volume = sum(material.volume for material in self.materials) | ||
| 194 | return total_volume >= volume_needed | 204 | return total_volume >= volume_needed | ||
| 195 | 205 | ||||
| 196 | @classmethod | 206 | @classmethod | ||
| 197 | def can_build_together(cls, volume_needed): | 207 | def can_build_together(cls, volume_needed): | ||
| 198 | # Calculate total volume of all materials across all factories | 208 | # Calculate total volume of all materials across all factories | ||
| 199 | total_volume = sum(material.volume for material in cls.all_materials) | 209 | total_volume = sum(material.volume for material in cls.all_materials) | ||
| 200 | return total_volume >= volume_needed | 210 | return total_volume >= volume_needed |
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
| |||||||||
| f | 1 | MATERIAL_BITS = { | f | 1 | MATERIAL_BITS = { |
| 2 | 'Concrete': 1 << 0, # 1 (binary 00001) | 2 | 'Concrete': 1 << 0, # 1 (binary 00001) | ||
| 3 | 'Brick': 1 << 1, # 2 (binary 00010) | 3 | 'Brick': 1 << 1, # 2 (binary 00010) | ||
| 4 | 'Stone': 1 << 2, # 4 (binary 00100) | 4 | 'Stone': 1 << 2, # 4 (binary 00100) | ||
| 5 | 'Wood': 1 << 3, # 8 (binary 01000) | 5 | 'Wood': 1 << 3, # 8 (binary 01000) | ||
| 6 | 'Steel': 1 << 4, # 16 (binary 10000) | 6 | 'Steel': 1 << 4, # 16 (binary 10000) | ||
| 7 | } | 7 | } | ||
| 8 | 8 | ||||
| 9 | BASE_MATERIAL_DENSITIES = { | 9 | BASE_MATERIAL_DENSITIES = { | ||
| 10 | 'Concrete': 2500, | 10 | 'Concrete': 2500, | ||
| 11 | 'Brick': 2000, | 11 | 'Brick': 2000, | ||
| 12 | 'Stone': 1600, | 12 | 'Stone': 1600, | ||
| 13 | 'Wood': 600, | 13 | 'Wood': 600, | ||
| 14 | 'Steel': 7700, | 14 | 'Steel': 7700, | ||
| 15 | } | 15 | } | ||
| 16 | 16 | ||||
| 17 | 17 | ||||
| 18 | class Material: | 18 | class Material: | ||
| 19 | def __init__(self, mass: float): | 19 | def __init__(self, mass: float): | ||
| 20 | if mass <= 0: | 20 | if mass <= 0: | ||
| 21 | raise ValueError("Mass must be a positive number.") | 21 | raise ValueError("Mass must be a positive number.") | ||
| 22 | self.mass: float = mass | 22 | self.mass: float = mass | ||
| 23 | self.used: bool = False | 23 | self.used: bool = False | ||
| 24 | self._volume: float = None # Cached value for the volume | 24 | self._volume: float = None # Cached value for the volume | ||
| 25 | 25 | ||||
| 26 | def __hash__(self): | 26 | def __hash__(self): | ||
| 27 | return id(self) # Hashing the object by its unique id | 27 | return id(self) # Hashing the object by its unique id | ||
| 28 | 28 | ||||
| 29 | @property | 29 | @property | ||
| 30 | def volume(self): | 30 | def volume(self): | ||
| 31 | if self._volume is None: | 31 | if self._volume is None: | ||
| 32 | self._volume = self.mass / self.density | 32 | self._volume = self.mass / self.density | ||
| 33 | return self._volume | 33 | return self._volume | ||
| 34 | 34 | ||||
| 35 | 35 | ||||
| 36 | class Concrete(Material): | 36 | class Concrete(Material): | ||
| 37 | density = BASE_MATERIAL_DENSITIES['Concrete'] | 37 | density = BASE_MATERIAL_DENSITIES['Concrete'] | ||
| 38 | material_bit = MATERIAL_BITS['Concrete'] | 38 | material_bit = MATERIAL_BITS['Concrete'] | ||
| 39 | 39 | ||||
| 40 | 40 | ||||
| 41 | class Brick(Material): | 41 | class Brick(Material): | ||
| 42 | density = BASE_MATERIAL_DENSITIES['Brick'] | 42 | density = BASE_MATERIAL_DENSITIES['Brick'] | ||
| 43 | material_bit = MATERIAL_BITS['Brick'] | 43 | material_bit = MATERIAL_BITS['Brick'] | ||
| 44 | 44 | ||||
| 45 | 45 | ||||
| 46 | class Stone(Material): | 46 | class Stone(Material): | ||
| 47 | density = BASE_MATERIAL_DENSITIES['Stone'] | 47 | density = BASE_MATERIAL_DENSITIES['Stone'] | ||
| 48 | material_bit = MATERIAL_BITS['Stone'] | 48 | material_bit = MATERIAL_BITS['Stone'] | ||
| 49 | 49 | ||||
| 50 | 50 | ||||
| 51 | class Wood(Material): | 51 | class Wood(Material): | ||
| 52 | density = BASE_MATERIAL_DENSITIES['Wood'] | 52 | density = BASE_MATERIAL_DENSITIES['Wood'] | ||
| 53 | material_bit = MATERIAL_BITS['Wood'] | 53 | material_bit = MATERIAL_BITS['Wood'] | ||
| 54 | 54 | ||||
| 55 | 55 | ||||
| 56 | class Steel(Material): | 56 | class Steel(Material): | ||
| 57 | density = BASE_MATERIAL_DENSITIES['Steel'] | 57 | density = BASE_MATERIAL_DENSITIES['Steel'] | ||
| 58 | material_bit = MATERIAL_BITS['Steel'] | 58 | material_bit = MATERIAL_BITS['Steel'] | ||
| 59 | 59 | ||||
| 60 | 60 | ||||
| 61 | class Factory: | 61 | class Factory: | ||
| 62 | material_classes = { | 62 | material_classes = { | ||
| 63 | 'Concrete': Concrete, | 63 | 'Concrete': Concrete, | ||
| 64 | 'Brick': Brick, | 64 | 'Brick': Brick, | ||
| 65 | 'Stone': Stone, | 65 | 'Stone': Stone, | ||
| 66 | 'Wood': Wood, | 66 | 'Wood': Wood, | ||
| 67 | 'Steel': Steel, | 67 | 'Steel': Steel, | ||
| 68 | } | 68 | } | ||
| n | 69 | dynamic_classes = {} # Stores dynamically created composite material classes | n | 69 | dynamic_classes = {} |
| 70 | all_materials = set() # Set of all material instances created across all factories | 70 | all_materials = set() | ||
| 71 | |||||
| 72 | # Precompute bitmask to material names mapping for quick lookup | ||||
| 73 | _bitmask_to_materials = { | ||||
| 74 | bitmask: [name for name, bit in MATERIAL_BITS.items() if bitmask & bit] | ||||
| 75 | for bitmask in range(1, 1 << len(MATERIAL_BITS)) | ||||
| 76 | } | ||||
| 71 | 77 | ||||
| 72 | def __init__(self): | 78 | def __init__(self): | ||
| n | n | 79 | self.material_bitmask = 0 # Represent current materials as a bitmask | ||
| 73 | self.materials = set() # Set of material instances created by this factory | 80 | self.materials = set() # Set of Material instances managed by this factory | ||
| 74 | 81 | ||||
| 75 | def __call__(self, *args, **kwargs): | 82 | def __call__(self, *args, **kwargs): | ||
| 76 | self._validate_call(args, kwargs) | 83 | self._validate_call(args, kwargs) | ||
| 77 | 84 | ||||
| 78 | if kwargs: | 85 | if kwargs: | ||
| 79 | return self._create_materials(kwargs) | 86 | return self._create_materials(kwargs) | ||
| 80 | else: | 87 | else: | ||
| 81 | return self._use_materials(args) | 88 | return self._use_materials(args) | ||
| 82 | 89 | ||||
| 83 | def _validate_call(self, args, kwargs): | 90 | def _validate_call(self, args, kwargs): | ||
| 84 | if (args and kwargs) or (not args and not kwargs): | 91 | if (args and kwargs) or (not args and not kwargs): | ||
| 85 | raise ValueError("Invalid input.") | 92 | raise ValueError("Invalid input.") | ||
| 86 | 93 | ||||
| 87 | def _create_materials(self, kwargs): | 94 | def _create_materials(self, kwargs): | ||
| 88 | materials_created = [] | 95 | materials_created = [] | ||
| 89 | for material_name, mass in kwargs.items(): | 96 | for material_name, mass in kwargs.items(): | ||
| 90 | material_class = self._get_material_class(material_name) | 97 | material_class = self._get_material_class(material_name) | ||
| 91 | material = self._instantiate_material(material_class, mass) | 98 | material = self._instantiate_material(material_class, mass) | ||
| 92 | self._add_material(material) | 99 | self._add_material(material) | ||
| 93 | materials_created.append(material) | 100 | materials_created.append(material) | ||
| n | n | 101 | self.material_bitmask |= MATERIAL_BITS[material_name] # Set the bit for the created material | ||
| 94 | return tuple(materials_created) | 102 | return tuple(materials_created) | ||
| 95 | 103 | ||||
| 96 | def _get_material_class(self, material_name): | 104 | def _get_material_class(self, material_name): | ||
| 97 | material_class = self.material_classes.get(material_name) or self.dynamic_classes.get(material_name) | 105 | material_class = self.material_classes.get(material_name) or self.dynamic_classes.get(material_name) | ||
| 98 | if material_class is None: | 106 | if material_class is None: | ||
| 99 | raise ValueError(f"Unknown material: {material_name}") | 107 | raise ValueError(f"Unknown material: {material_name}") | ||
| 100 | return material_class | 108 | return material_class | ||
| 101 | 109 | ||||
| 102 | def _instantiate_material(self, material_class, mass): | 110 | def _instantiate_material(self, material_class, mass): | ||
| 103 | return material_class(mass) | 111 | return material_class(mass) | ||
| 104 | 112 | ||||
| 105 | def _add_material(self, material): | 113 | def _add_material(self, material): | ||
| n | 106 | self.materials.add(material) | n | 114 | self.materials.add(material) # Add to factory's materials |
| 107 | Factory.all_materials.add(material) | 115 | Factory.all_materials.add(material) # Add to global all_materials | ||
| 116 | # Update the bitmask with the material's bit | ||||
| 117 | self.material_bitmask |= material.material_bit # Already done in _create_materials | ||||
| 108 | 118 | ||||
| 109 | def _use_materials(self, materials): | 119 | def _use_materials(self, materials): | ||
| 110 | self._validate_materials(materials) | 120 | self._validate_materials(materials) | ||
| 111 | self._mark_materials_as_used(materials) | 121 | self._mark_materials_as_used(materials) | ||
| 112 | 122 | ||||
| 113 | material_bitmask, total_mass = self._calculate_bitmask_and_mass(materials) | 123 | material_bitmask, total_mass = self._calculate_bitmask_and_mass(materials) | ||
| 114 | new_class = self._get_or_create_dynamic_class(material_bitmask) | 124 | new_class = self._get_or_create_dynamic_class(material_bitmask) | ||
| 115 | 125 | ||||
| 116 | new_material = self._create_new_material(new_class, total_mass) | 126 | new_material = self._create_new_material(new_class, total_mass) | ||
| n | n | 127 | self.material_bitmask |= new_class.material_bit # Set the bit for the new composite material | ||
| 117 | return new_material | 128 | return new_material | ||
| 118 | 129 | ||||
| 119 | def _validate_materials(self, materials): | 130 | def _validate_materials(self, materials): | ||
| 120 | known_classes = tuple(self.material_classes.values()) + tuple(self.dynamic_classes.values()) | 131 | known_classes = tuple(self.material_classes.values()) + tuple(self.dynamic_classes.values()) | ||
| 121 | 132 | ||||
| 122 | for material in materials: | 133 | for material in materials: | ||
| 123 | if not isinstance(material, known_classes): | 134 | if not isinstance(material, known_classes): | ||
| 124 | raise ValueError(f"Invalid material instance: {material}") | 135 | raise ValueError(f"Invalid material instance: {material}") | ||
| 125 | if material.used: | 136 | if material.used: | ||
| 126 | raise AssertionError(f"Material {material} is already used.") | 137 | raise AssertionError(f"Material {material} is already used.") | ||
| 127 | 138 | ||||
| 128 | def _mark_materials_as_used(self, materials): | 139 | def _mark_materials_as_used(self, materials): | ||
| 129 | for material in materials: | 140 | for material in materials: | ||
| 130 | material.used = True | 141 | material.used = True | ||
| n | 131 | self.materials.discard(material) | n | 142 | self.materials.discard(material) # Remove from factory's materials |
| 132 | Factory.all_materials.discard(material) | 143 | self.material_bitmask &= ~material.material_bit # Clear the bit for the used material | ||
| 144 | Factory.all_materials.discard(material) # Remove from global all_materials | ||||
| 133 | 145 | ||||
| 134 | def _calculate_bitmask_and_mass(self, materials): | 146 | def _calculate_bitmask_and_mass(self, materials): | ||
| 135 | material_bitmask = 0 | 147 | material_bitmask = 0 | ||
| 136 | total_mass = 0 | 148 | total_mass = 0 | ||
| 137 | for material in materials: | 149 | for material in materials: | ||
| 138 | material_bitmask |= material.material_bit # Combine material bits using bitwise OR | 150 | material_bitmask |= material.material_bit # Combine material bits using bitwise OR | ||
| 139 | total_mass += material.mass | 151 | total_mass += material.mass | ||
| 140 | return material_bitmask, total_mass | 152 | return material_bitmask, total_mass | ||
| 141 | 153 | ||||
| 142 | def _get_or_create_dynamic_class(self, material_bitmask): | 154 | def _get_or_create_dynamic_class(self, material_bitmask): | ||
| 143 | if material_bitmask in self.dynamic_classes: | 155 | if material_bitmask in self.dynamic_classes: | ||
| 144 | return self.dynamic_classes[material_bitmask] | 156 | return self.dynamic_classes[material_bitmask] | ||
| 145 | else: | 157 | else: | ||
| 146 | return self._create_dynamic_class(material_bitmask) | 158 | return self._create_dynamic_class(material_bitmask) | ||
| 147 | 159 | ||||
| 148 | def _create_dynamic_class(self, material_bitmask): | 160 | def _create_dynamic_class(self, material_bitmask): | ||
| 149 | base_materials = self._determine_base_materials(material_bitmask) | 161 | base_materials = self._determine_base_materials(material_bitmask) | ||
| 150 | density = self._calculate_density(base_materials) | 162 | density = self._calculate_density(base_materials) | ||
| 151 | class_name = self._generate_class_name(base_materials) | 163 | class_name = self._generate_class_name(base_materials) | ||
| 152 | 164 | ||||
| 153 | attributes = { | 165 | attributes = { | ||
| 154 | 'density': density, | 166 | 'density': density, | ||
| 155 | 'material_bit': material_bitmask, | 167 | 'material_bit': material_bitmask, | ||
| 156 | } | 168 | } | ||
| 157 | new_class = type(class_name, (Material,), attributes) | 169 | new_class = type(class_name, (Material,), attributes) | ||
| 158 | self.dynamic_classes[material_bitmask] = new_class | 170 | self.dynamic_classes[material_bitmask] = new_class | ||
| 159 | return new_class | 171 | return new_class | ||
| 160 | 172 | ||||
| 161 | def _determine_base_materials(self, material_bitmask): | 173 | def _determine_base_materials(self, material_bitmask): | ||
| n | 162 | return [name for name, bit in MATERIAL_BITS.items() if material_bitmask & bit] # Check if material is included using bitwise AND | n | 174 | # Use precomputed bitmask to materials mapping for efficiency |
| 175 | return self._bitmask_to_materials.get(material_bitmask, []) | ||||
| 163 | 176 | ||||
| 164 | def _calculate_density(self, base_materials): | 177 | def _calculate_density(self, base_materials): | ||
| 165 | densities = [BASE_MATERIAL_DENSITIES[name] for name in base_materials] | 178 | densities = [BASE_MATERIAL_DENSITIES[name] for name in base_materials] | ||
| 166 | return sum(densities) / len(densities) | 179 | return sum(densities) / len(densities) | ||
| 167 | 180 | ||||
| 168 | def _generate_class_name(self, base_materials): | 181 | def _generate_class_name(self, base_materials): | ||
| n | 169 | return '_'.join(sorted(base_materials)) # Generate class name by sorting material names | n | 182 | # Generate class name by sorting material names to ensure consistency |
| 183 | return '_'.join(sorted(base_materials)) | ||||
| 170 | 184 | ||||
| 171 | def _create_new_material(self, new_class, total_mass): | 185 | def _create_new_material(self, new_class, total_mass): | ||
| 172 | new_material = new_class(total_mass) | 186 | new_material = new_class(total_mass) | ||
| n | 173 | self.materials.add(new_material) | n | 187 | self.materials.add(new_material) # Add to factory's materials |
| 174 | Factory.all_materials.add(new_material) | 188 | Factory.all_materials.add(new_material) # Add to global all_materials | ||
| 175 | return new_material | 189 | return new_material | ||
| 176 | 190 | ||||
| 177 | def can_build(self, volume_needed): | 191 | def can_build(self, volume_needed): | ||
| n | n | 192 | # Calculate total volume of current materials in this factory | ||
| 178 | total_volume = sum(material.volume for material in self.materials) | 193 | total_volume = sum(material.volume for material in self.materials) | ||
| 179 | return total_volume >= volume_needed | 194 | return total_volume >= volume_needed | ||
| 180 | 195 | ||||
| 181 | @classmethod | 196 | @classmethod | ||
| 182 | def can_build_together(cls, volume_needed): | 197 | def can_build_together(cls, volume_needed): | ||
| t | t | 198 | # Calculate total volume of all materials across all factories | ||
| 183 | total_volume = sum(material.volume for material in cls.all_materials) | 199 | total_volume = sum(material.volume for material in cls.all_materials) | ||
| 184 | return total_volume >= volume_needed | 200 | return total_volume >= volume_needed |
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
| |||||||||
| n | 1 | class Material: | n | ||
| 2 | def __init__(self, mass): | ||||
| 3 | self.mass = mass | ||||
| 4 | self.used = False | ||||
| 5 | self._volume = None # Cached value for the volume | ||||
| 6 | |||||
| 7 | def __hash__(self): | ||||
| 8 | return id(self) # Hashing the object by its unique id | ||||
| 9 | |||||
| 10 | def __eq__(self, other): | ||||
| 11 | return self is other | ||||
| 12 | |||||
| 13 | @property | ||||
| 14 | def volume(self): # Caching the Volume Calculation | ||||
| 15 | if self._volume is None: | ||||
| 16 | self._volume = round(self.mass / self.density, 2) | ||||
| 17 | return self._volume | ||||
| 18 | |||||
| 19 | |||||
| 20 | MATERIAL_BITS = { | 1 | MATERIAL_BITS = { | ||
| 21 | 'Concrete': 1 << 0, # 1 (binary 00001) | 2 | 'Concrete': 1 << 0, # 1 (binary 00001) | ||
| 22 | 'Brick': 1 << 1, # 2 (binary 00010) | 3 | 'Brick': 1 << 1, # 2 (binary 00010) | ||
| 23 | 'Stone': 1 << 2, # 4 (binary 00100) | 4 | 'Stone': 1 << 2, # 4 (binary 00100) | ||
| 24 | 'Wood': 1 << 3, # 8 (binary 01000) | 5 | 'Wood': 1 << 3, # 8 (binary 01000) | ||
| 25 | 'Steel': 1 << 4, # 16 (binary 10000) | 6 | 'Steel': 1 << 4, # 16 (binary 10000) | ||
| 26 | } | 7 | } | ||
| 27 | 8 | ||||
| 28 | BASE_MATERIAL_DENSITIES = { | 9 | BASE_MATERIAL_DENSITIES = { | ||
| 29 | 'Concrete': 2500, | 10 | 'Concrete': 2500, | ||
| 30 | 'Brick': 2000, | 11 | 'Brick': 2000, | ||
| 31 | 'Stone': 1600, | 12 | 'Stone': 1600, | ||
| 32 | 'Wood': 600, | 13 | 'Wood': 600, | ||
| 33 | 'Steel': 7700, | 14 | 'Steel': 7700, | ||
| 34 | } | 15 | } | ||
| 35 | 16 | ||||
| n | n | 17 | |||
| 18 | class Material: | ||||
| 19 | def __init__(self, mass: float): | ||||
| 20 | if mass <= 0: | ||||
| 21 | raise ValueError("Mass must be a positive number.") | ||||
| 22 | self.mass: float = mass | ||||
| 23 | self.used: bool = False | ||||
| 24 | self._volume: float = None # Cached value for the volume | ||||
| 25 | |||||
| 26 | def __hash__(self): | ||||
| 27 | return id(self) # Hashing the object by its unique id | ||||
| 28 | |||||
| 29 | @property | ||||
| 30 | def volume(self): | ||||
| 31 | if self._volume is None: | ||||
| 32 | self._volume = self.mass / self.density | ||||
| 33 | return self._volume | ||||
| 34 | |||||
| 35 | |||||
| 36 | class Concrete(Material): | 36 | class Concrete(Material): | ||
| n | 37 | density = 2500 | n | 37 | density = BASE_MATERIAL_DENSITIES['Concrete'] |
| 38 | base_materials = {'Concrete'} | ||||
| 39 | material_bit = MATERIAL_BITS['Concrete'] | 38 | material_bit = MATERIAL_BITS['Concrete'] | ||
| 40 | 39 | ||||
| 41 | 40 | ||||
| 42 | class Brick(Material): | 41 | class Brick(Material): | ||
| n | 43 | density = 2000 | n | 42 | density = BASE_MATERIAL_DENSITIES['Brick'] |
| 44 | base_materials = {'Brick'} | ||||
| 45 | material_bit = MATERIAL_BITS['Brick'] | 43 | material_bit = MATERIAL_BITS['Brick'] | ||
| 46 | 44 | ||||
| 47 | 45 | ||||
| 48 | class Stone(Material): | 46 | class Stone(Material): | ||
| n | 49 | density = 1600 | n | 47 | density = BASE_MATERIAL_DENSITIES['Stone'] |
| 50 | base_materials = {'Stone'} | ||||
| 51 | material_bit = MATERIAL_BITS['Stone'] | 48 | material_bit = MATERIAL_BITS['Stone'] | ||
| 52 | 49 | ||||
| 53 | 50 | ||||
| 54 | class Wood(Material): | 51 | class Wood(Material): | ||
| n | 55 | density = 600 | n | 52 | density = BASE_MATERIAL_DENSITIES['Wood'] |
| 56 | base_materials = {'Wood'} | ||||
| 57 | material_bit = MATERIAL_BITS['Wood'] | 53 | material_bit = MATERIAL_BITS['Wood'] | ||
| 58 | 54 | ||||
| 59 | 55 | ||||
| 60 | class Steel(Material): | 56 | class Steel(Material): | ||
| n | 61 | density = 7700 | n | 57 | density = BASE_MATERIAL_DENSITIES['Steel'] |
| 62 | base_materials = {'Steel'} | ||||
| 63 | material_bit = MATERIAL_BITS['Steel'] | 58 | material_bit = MATERIAL_BITS['Steel'] | ||
| 64 | 59 | ||||
| 65 | 60 | ||||
| 66 | class Factory: | 61 | class Factory: | ||
| 67 | material_classes = { | 62 | material_classes = { | ||
| 68 | 'Concrete': Concrete, | 63 | 'Concrete': Concrete, | ||
| 69 | 'Brick': Brick, | 64 | 'Brick': Brick, | ||
| 70 | 'Stone': Stone, | 65 | 'Stone': Stone, | ||
| 71 | 'Wood': Wood, | 66 | 'Wood': Wood, | ||
| 72 | 'Steel': Steel, | 67 | 'Steel': Steel, | ||
| 73 | } | 68 | } | ||
| 74 | dynamic_classes = {} # Stores dynamically created composite material classes | 69 | dynamic_classes = {} # Stores dynamically created composite material classes | ||
| 75 | all_materials = set() # Set of all material instances created across all factories | 70 | all_materials = set() # Set of all material instances created across all factories | ||
| 76 | 71 | ||||
| 77 | def __init__(self): | 72 | def __init__(self): | ||
| 78 | self.materials = set() # Set of material instances created by this factory | 73 | self.materials = set() # Set of material instances created by this factory | ||
| 79 | 74 | ||||
| 80 | def __call__(self, *args, **kwargs): | 75 | def __call__(self, *args, **kwargs): | ||
| n | 81 | if (args and kwargs) or (not args and not kwargs): | n | 76 | self._validate_call(args, kwargs) |
| 82 | raise ValueError("Error") | ||||
| 83 | |||||
| 84 | # Local references for faster access | ||||
| 85 | material_classes = Factory.material_classes | ||||
| 86 | dynamic_classes = Factory.dynamic_classes | ||||
| 87 | all_materials = Factory.all_materials | ||||
| 88 | |||||
| 89 | known_classes = tuple(material_classes.values()) + tuple(dynamic_classes.values()) | ||||
| 90 | 77 | ||||
| 91 | if kwargs: | 78 | if kwargs: | ||
| n | 92 | materials_created = [] | n | 79 | return self._create_materials(kwargs) |
| 93 | for material_name, mass in kwargs.items(): | ||||
| 94 | material_class = material_classes.get(material_name) or dynamic_classes.get(material_name) | ||||
| 95 | if material_class is None: | ||||
| 96 | raise ValueError(f"Unknown material: {material_name}") | ||||
| 97 | material = material_class(mass) | ||||
| 98 | self.materials.add(material) | ||||
| 99 | all_materials.add(material) | ||||
| 100 | materials_created.append(material) | ||||
| 101 | return tuple(materials_created) | ||||
| 102 | else: | 80 | else: | ||
| n | 103 | for material in args: | n | 81 | return self._use_materials(args) |
| 104 | if not isinstance(material, known_classes): | ||||
| 105 | raise ValueError("Error") | ||||
| 106 | if material.used: | ||||
| 107 | raise AssertionError("Error") | ||||
| 108 | for material in args: | ||||
| 109 | material.used = True | ||||
| 110 | self.materials.discard(material) | ||||
| 111 | all_materials.discard(material) | ||||
| 112 | 82 | ||||
| n | 113 | material_bitmask = 0 | n | 83 | def _validate_call(self, args, kwargs): |
| 114 | total_mass = 0 | 84 | if (args and kwargs) or (not args and not kwargs): | ||
| 115 | for material in args: | 85 | raise ValueError("Invalid input.") | ||
| 116 | material_bitmask |= material.material_bit # Combine material bits using bitwise OR | ||||
| 117 | total_mass += material.mass | ||||
| 118 | 86 | ||||
| n | 119 | if material_bitmask in dynamic_classes: | n | 87 | def _create_materials(self, kwargs): |
| 120 | new_class = dynamic_classes[material_bitmask] | 88 | materials_created = [] | ||
| 121 | else: | 89 | for material_name, mass in kwargs.items(): | ||
| 122 | # Determine base materials and their densities from the material_bitmask | 90 | material_class = self._get_material_class(material_name) | ||
| 123 | base_materials = [] | 91 | material = self._instantiate_material(material_class, mass) | ||
| 124 | densities = [] | 92 | self._add_material(material) | ||
| 125 | for material_name, bit in MATERIAL_BITS.items(): | 93 | materials_created.append(material) | ||
| 126 | if material_bitmask & bit: # Check if material is included using bitwise AND | 94 | return tuple(materials_created) | ||
| 127 | base_materials.append(material_name) | ||||
| 128 | densities.append(BASE_MATERIAL_DENSITIES[material_name]) | ||||
| 129 | density = sum(densities) / len(densities) | ||||
| 130 | class_name = '_'.join(sorted(base_materials)) | ||||
| 131 | 95 | ||||
| n | 132 | attributes = { | n | 96 | def _get_material_class(self, material_name): |
| 133 | 'density': density, | 97 | material_class = self.material_classes.get(material_name) or self.dynamic_classes.get(material_name) | ||
| 134 | 'base_materials': set(base_materials), | 98 | if material_class is None: | ||
| 135 | 'material_bit': material_bitmask, | 99 | raise ValueError(f"Unknown material: {material_name}") | ||
| 136 | } | 100 | return material_class | ||
| 137 | new_class = type(class_name, (Material,), attributes) | ||||
| 138 | dynamic_classes[material_bitmask] = new_class | ||||
| 139 | 101 | ||||
| n | n | 102 | def _instantiate_material(self, material_class, mass): | ||
| 103 | return material_class(mass) | ||||
| 104 | |||||
| 105 | def _add_material(self, material): | ||||
| 106 | self.materials.add(material) | ||||
| 107 | Factory.all_materials.add(material) | ||||
| 108 | |||||
| 109 | def _use_materials(self, materials): | ||||
| 110 | self._validate_materials(materials) | ||||
| 111 | self._mark_materials_as_used(materials) | ||||
| 112 | |||||
| 113 | material_bitmask, total_mass = self._calculate_bitmask_and_mass(materials) | ||||
| 114 | new_class = self._get_or_create_dynamic_class(material_bitmask) | ||||
| 115 | |||||
| 116 | new_material = self._create_new_material(new_class, total_mass) | ||||
| 117 | return new_material | ||||
| 118 | |||||
| 119 | def _validate_materials(self, materials): | ||||
| 120 | known_classes = tuple(self.material_classes.values()) + tuple(self.dynamic_classes.values()) | ||||
| 121 | |||||
| 122 | for material in materials: | ||||
| 123 | if not isinstance(material, known_classes): | ||||
| 124 | raise ValueError(f"Invalid material instance: {material}") | ||||
| 125 | if material.used: | ||||
| 126 | raise AssertionError(f"Material {material} is already used.") | ||||
| 127 | |||||
| 128 | def _mark_materials_as_used(self, materials): | ||||
| 129 | for material in materials: | ||||
| 130 | material.used = True | ||||
| 131 | self.materials.discard(material) | ||||
| 132 | Factory.all_materials.discard(material) | ||||
| 133 | |||||
| 134 | def _calculate_bitmask_and_mass(self, materials): | ||||
| 135 | material_bitmask = 0 | ||||
| 136 | total_mass = 0 | ||||
| 137 | for material in materials: | ||||
| 138 | material_bitmask |= material.material_bit # Combine material bits using bitwise OR | ||||
| 139 | total_mass += material.mass | ||||
| 140 | return material_bitmask, total_mass | ||||
| 141 | |||||
| 142 | def _get_or_create_dynamic_class(self, material_bitmask): | ||||
| 143 | if material_bitmask in self.dynamic_classes: | ||||
| 144 | return self.dynamic_classes[material_bitmask] | ||||
| 145 | else: | ||||
| 146 | return self._create_dynamic_class(material_bitmask) | ||||
| 147 | |||||
| 148 | def _create_dynamic_class(self, material_bitmask): | ||||
| 149 | base_materials = self._determine_base_materials(material_bitmask) | ||||
| 150 | density = self._calculate_density(base_materials) | ||||
| 151 | class_name = self._generate_class_name(base_materials) | ||||
| 152 | |||||
| 153 | attributes = { | ||||
| 154 | 'density': density, | ||||
| 155 | 'material_bit': material_bitmask, | ||||
| 156 | } | ||||
| 157 | new_class = type(class_name, (Material,), attributes) | ||||
| 158 | self.dynamic_classes[material_bitmask] = new_class | ||||
| 159 | return new_class | ||||
| 160 | |||||
| 161 | def _determine_base_materials(self, material_bitmask): | ||||
| 162 | return [name for name, bit in MATERIAL_BITS.items() if material_bitmask & bit] # Check if material is included using bitwise AND | ||||
| 163 | |||||
| 164 | def _calculate_density(self, base_materials): | ||||
| 165 | densities = [BASE_MATERIAL_DENSITIES[name] for name in base_materials] | ||||
| 166 | return sum(densities) / len(densities) | ||||
| 167 | |||||
| 168 | def _generate_class_name(self, base_materials): | ||||
| 169 | return '_'.join(sorted(base_materials)) # Generate class name by sorting material names | ||||
| 170 | |||||
| 171 | def _create_new_material(self, new_class, total_mass): | ||||
| 140 | new_material = new_class(total_mass) | 172 | new_material = new_class(total_mass) | ||
| 141 | self.materials.add(new_material) | 173 | self.materials.add(new_material) | ||
| 142 | all_materials.add(new_material) | 174 | Factory.all_materials.add(new_material) | ||
| 143 | return new_material | 175 | return new_material | ||
| 144 | 176 | ||||
| 145 | def can_build(self, volume_needed): | 177 | def can_build(self, volume_needed): | ||
| n | 146 | total_volume = 0 | n | 178 | total_volume = sum(material.volume for material in self.materials) |
| 147 | for material in self.materials: | ||||
| 148 | total_volume += material.volume | ||||
| 149 | if total_volume >= volume_needed: | 179 | return total_volume >= volume_needed | ||
| 150 | return True # Early exit if required volume is met | ||||
| 151 | return False | ||||
| 152 | 180 | ||||
| 153 | @classmethod | 181 | @classmethod | ||
| 154 | def can_build_together(cls, volume_needed): | 182 | def can_build_together(cls, volume_needed): | ||
| t | 155 | total_volume = 0 | t | 183 | total_volume = sum(material.volume for material in cls.all_materials) |
| 156 | for material in cls.all_materials: | ||||
| 157 | total_volume += material.volume | ||||
| 158 | if total_volume >= volume_needed: | 184 | return total_volume >= volume_needed | ||
| 159 | return True # Early exit if required volume is met | ||||
| 160 | return False |
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
| |||||||||