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