f | class Material: | f | class Material: |
| """Parent class for all the materials.""" | | """Parent class for all the materials.""" |
| | | |
| DENSITY = 0 | | DENSITY = 0 |
| | | |
| def __init__(self, mass): | | def __init__(self, mass): |
| self.mass = mass | | self.mass = mass |
| self.is_used = False | | self.is_used = False |
n | | n | self.BASIC_CLASSES = (type(self),) |
| | | |
| @property | | @property |
| def volume(self): | | def volume(self): |
| return self.mass / self.DENSITY | | return self.mass / self.DENSITY |
| | | |
| | | |
| class Concrete(Material): | | class Concrete(Material): |
| """Create concrete with mass, density and volume.""" | | """Create concrete with mass, density and volume.""" |
| | | |
| DENSITY = 2500 | | DENSITY = 2500 |
| | | |
| | | |
| class Brick(Material): | | class Brick(Material): |
| """Create brick with mass, density and volume.""" | | """Create brick with mass, density and volume.""" |
| | | |
| DENSITY = 2000 | | DENSITY = 2000 |
| | | |
| | | |
| class Stone(Material): | | class Stone(Material): |
| """Create stone with mass, density and volume.""" | | """Create stone with mass, density and volume.""" |
| | | |
| DENSITY = 1600 | | DENSITY = 1600 |
| | | |
| | | |
| class Wood(Material): | | class Wood(Material): |
| """Create wood with mass, density and volume.""" | | """Create wood with mass, density and volume.""" |
| | | |
| DENSITY = 600 | | DENSITY = 600 |
| | | |
| | | |
| class Steel(Material): | | class Steel(Material): |
| """Create steel with mass, density and volume.""" | | """Create steel with mass, density and volume.""" |
| | | |
| DENSITY = 7700 | | DENSITY = 7700 |
| | | |
| | | |
| class Factory: | | class Factory: |
| """Create a factory that is callable.""" | | """Create a factory that is callable.""" |
| | | |
| MATERIAL_PARENT = Material | | MATERIAL_PARENT = Material |
| all_factories = [] | | all_factories = [] |
| | | |
| def __init__(self): | | def __init__(self): |
| self.materials = [] | | self.materials = [] |
| self.all_factories.append(self) | | self.all_factories.append(self) |
| | | |
| def __call__(self, *args, **kwargs): | | def __call__(self, *args, **kwargs): |
| if not self.__check_for_valid_input(*args, **kwargs): | | if not self.__check_for_valid_input(*args, **kwargs): |
| raise ValueError('Cannot create factory') | | raise ValueError('Cannot create factory') |
| | | |
| if args: | | if args: |
n | try: | n | |
| return self.__create_new_material(*args) | | return self.__create_new_material(*args) |
| except Exception: | | |
| raise | | |
| else: | | else: |
n | try: | n | |
| return self.__init_basic_materials(**kwargs) | | return self.__create_known_materials(**kwargs) |
| except Exception: | | |
| raise | | |
| | | |
| def __check_for_valid_input(self, *args, **kwargs): | | def __check_for_valid_input(self, *args, **kwargs): |
n | len_args = len(args) | n | |
| len_kwargs = len(kwargs) | | |
| if bool(len_args) == bool(len_kwargs): | | return bool(args) ^ bool(kwargs) |
| return False | | |
| return True | | |
| | | |
| def __create_new_material(self, *args): | | def __create_new_material(self, *args): |
| material_classes = {cls.__name__: cls for cls in self.MATERIAL_PARENT.__subclasses__()} | | material_classes = {cls.__name__: cls for cls in self.MATERIAL_PARENT.__subclasses__()} |
| # create new class name | | # create new class name |
n | classes_for_new_material = [] | n | classes_for_new_material = set() |
| for inst in args: | | for inst in args: |
| if type(inst) not in material_classes.values(): | | if type(inst) not in material_classes.values(): |
| raise ValueError('Invalid material') | | raise ValueError('Invalid material') |
| if inst.is_used: | | if inst.is_used: |
| raise AssertionError('Material is already used') | | raise AssertionError('Material is already used') |
n | basic_classes = getattr(inst, 'BASIC_CLASSES', None) # if material is custom | n | |
| if basic_classes: | | |
| classes_for_new_material.extend((cls for cls in basic_classes if cls not in classes_for_new_material)) | | classes_for_new_material.update((cls for cls in inst.BASIC_CLASSES if cls not in classes_for_new_material)) |
| else: | | |
| classes_for_new_material.append(type(inst)) | | |
| inst.is_used = True | | inst.is_used = True |
n | | n | classes_for_new_material = list(classes_for_new_material) |
| classes_for_new_material.sort(key=lambda cls: cls.__name__) | | classes_for_new_material.sort(key=lambda cls: cls.__name__) |
| new_class_name = '_'.join((cls.__name__ for cls in classes_for_new_material)) | | new_class_name = '_'.join((cls.__name__ for cls in classes_for_new_material)) |
| | | |
| # calculate new mass | | # calculate new mass |
n | mass = sum((arg.mass for arg in args)) | n | mass = sum((inst.mass for inst in args)) |
| | | |
| # earlier exit if class exists | | # earlier exit if class exists |
n | if new_class_name in material_classes.keys(): | n | if new_class_name in material_classes: |
| new_material = material_classes[new_class_name](mass) | | new_material = material_classes[new_class_name](mass) |
| self.materials.append(new_material) | | self.materials.append(new_material) |
| return new_material | | return new_material |
| | | |
| # calculate new density | | # calculate new density |
| basic_densities = set() | | basic_densities = set() |
n | for arg in args: | n | for inst in args: |
| basic_classes = getattr(arg, 'BASIC_CLASSES', None) | | basic_densities.update(inst.BASIC_CLASSES) |
| if basic_classes: # if material is custom | | |
| basic_densities.update(basic_classes) | | |
| else: | | |
| basic_densities.add(type(arg)) | | |
| new_density = sum(cls.DENSITY for cls in basic_densities) / len(basic_densities) | | new_density = sum(cls.DENSITY for cls in basic_densities) / len(basic_densities) |
| | | |
| # create new class | | # create new class |
| new_class = type(new_class_name, | | new_class = type(new_class_name, |
| (self.MATERIAL_PARENT,), | | (self.MATERIAL_PARENT,), |
n | { | n | |
| 'DENSITY': new_density, | | { 'DENSITY': new_density }) |
| 'BASIC_CLASSES': classes_for_new_material | | |
| }) | | |
| new_material = new_class(mass) | | new_material = new_class(mass) |
n | | n | new_material.BASIC_CLASSES = tuple(classes_for_new_material) |
| self.materials.append(new_material) | | self.materials.append(new_material) |
| return new_material | | return new_material |
| | | |
t | def __init_basic_materials(self, **kwargs): | t | def __create_known_materials(self, **kwargs): |
| material_classes = {cls.__name__: cls for cls in self.MATERIAL_PARENT.__subclasses__()} | | material_classes = {cls.__name__: cls for cls in self.MATERIAL_PARENT.__subclasses__()} |
| new_materials = [] | | new_materials = [] |
| for key in kwargs.keys(): | | for key in kwargs.keys(): |
| cls = material_classes.get(key, None) | | cls = material_classes.get(key, None) |
| if not cls: | | if not cls: |
| raise ValueError('Invalid material name') | | raise ValueError('Invalid material name') |
| new_materials.append(cls(kwargs[key])) | | new_materials.append(cls(kwargs[key])) |
| | | |
| self.materials.extend(new_materials) | | self.materials.extend(new_materials) |
| return tuple(new_materials) | | return tuple(new_materials) |
| | | |
| @property | | @property |
| def volume_available(self): | | def volume_available(self): |
| return sum(mat.volume for mat in self.materials if not mat.is_used) | | return sum(mat.volume for mat in self.materials if not mat.is_used) |
| | | |
| def can_build(self, volume): | | def can_build(self, volume): |
| return self.volume_available >= volume | | return self.volume_available >= volume |
| | | |
| @classmethod | | @classmethod |
| def can_build_together(cls, volume_to_build): | | def can_build_together(cls, volume_to_build): |
| total_volume = sum(fact.volume_available for fact in cls.all_factories) | | total_volume = sum(fact.volume_available for fact in cls.all_factories) |
| return total_volume >= volume_to_build | | return total_volume >= volume_to_build |