1class Material:
  2    DENSITY = 0
  3
  4    def __init__(self, mass):
  5        self.mass = mass
  6
  7    @property
  8    def volume(self):
  9        return float(self.mass / self.DENSITY)
 10    
 11
 12class Concrete(Material):
 13    DENSITY = 2500
 14
 15
 16class Brick(Material):
 17    DENSITY = 2000
 18
 19
 20class Stone(Material):
 21    DENSITY = 1600
 22
 23
 24class Wood(Material):
 25    DENSITY = 600
 26
 27
 28class Steel(Material):
 29    DENSITY = 7700
 30
 31
 32class Factory:
 33    _MATERIALS = { 
 34        'Concrete': Concrete,
 35        'Brick': Brick,
 36        'Stone': Stone,
 37        'Wood': Wood,
 38        'Steel': Steel
 39        }
 40    
 41    _alloys = {}
 42    _all_materials = set()
 43    _used_materials = set()
 44   
 45    def __init__(self):
 46        self._curr_materials = set()
 47
 48    def __call__(self, *args, **kwargs):
 49        if args and kwargs:
 50            raise ValueError('Function can be called with only positional or only keyword arguments')
 51        elif not args and not kwargs:
 52            raise ValueError('Function cannot be called without arguments')
 53        elif kwargs:
 54            return self._get_result_from_kwargs(**kwargs) 
 55        elif args:
 56            return self._get_alloy_from_args(*args)
 57                
 58    def _get_result_from_kwargs(self, **kwargs):
 59        """Return tuple of instances of materials, created from keyword arguments."""  
 60        res = []
 61        for key, val in kwargs.items():
 62            material = self._MATERIALS.get(key)
 63            if material is None:
 64                material = Factory._alloys.get(key)
 65                
 66            if material is None or not issubclass(material, Material):
 67                raise ValueError('Invalid material')
 68            
 69            new_material = material(val)
 70            Factory._all_materials.add(new_material) 
 71            self._curr_materials.add(new_material)
 72            
 73            res.append(new_material)
 74
 75        return tuple(res)
 76    
 77    def _get_average_density(self, *args):
 78        """Return the average density of the materials."""
 79        density_sum = 0
 80        for arg in args:
 81            density_sum += type(arg).DENSITY
 82        
 83        average_density = density_sum / len(args)
 84        return average_density
 85    
 86    def _get_mass(self, *args):
 87        """Calculate the sum of the masses of the materials"""
 88        mass_sum = 0
 89        for arg in args:
 90            mass_sum += arg.mass
 91
 92        return mass_sum
 93
 94    def _get_alloy_from_args(self, *args):
 95        """Create a dynamic class (alloy) from materials."""
 96        base_classes_names = []
 97
 98        for arg in args:
 99            if arg in Factory._used_materials:
100                raise AssertionError('Invalid material')
101            
102            Factory._used_materials.add(arg)
103            
104            base_classes_names.extend(type(arg).__name__.split('_'))
105            
106        sorted_materials_names = sorted(base_classes_names)
107        new_alloy_name = '_'.join(sorted_materials_names)
108
109        if new_alloy_name not in Factory._alloys:
110            average_density = self._get_average_density(*args)
111            alloy = type(
112                new_alloy_name,
113                  (Material,), 
114                  {'DENSITY': average_density}
115                )
116            Factory._alloys[new_alloy_name] = alloy
117
118        alloy = Factory._alloys[new_alloy_name]
119        alloy_mass = self._get_mass(*args)
120        
121        new_alloy = alloy(alloy_mass)
122        self._curr_materials.add(new_alloy)
123        Factory._all_materials.add(new_alloy)
124        return new_alloy
125        
126    def can_build(self, needed_volume):
127        volume_sum = 0
128        for material in self._curr_materials:
129            if material not in Factory._used_materials:
130                volume_sum += material.volume
131        
132        return volume_sum >= needed_volume
133    
134    @classmethod
135    def can_build_together(cls, needed_volume):
136        volume_sum = 0
137        for material in cls._all_materials:
138            volume_sum += material.volume
139
140        return volume_sum >= needed_volume
.F...FFF..
======================================================================
FAIL: test_can_build (test.TestFactory.test_can_build)
Test can_build methods.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 177, in test_can_build
    self.assertFalse(solution.Factory.can_build_together(6.0001))
AssertionError: True is not false
======================================================================
FAIL: test_positional_arguments_multiple_argument_from_initial_set (test.TestFactory.test_positional_arguments_multiple_argument_from_initial_set)
Test calling a factory using multiple positional arguments.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 116, in test_positional_arguments_multiple_argument_from_initial_set
    concrete_wood = self.factory1(concrete, wood)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/solution.py", line 56, in __call__
    return self._get_alloy_from_args(*args)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/solution.py", line 100, in _get_alloy_from_args
    raise AssertionError('Invalid material')
AssertionError: Invalid material
======================================================================
FAIL: test_positional_arguments_multiple_argument_with_dynamics (test.TestFactory.test_positional_arguments_multiple_argument_with_dynamics)
Test calling a factory using multiple positional arguments.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 130, in test_positional_arguments_multiple_argument_with_dynamics
    self.assertAlmostEqual(mega_material.volume, 6.77, places=2)  # mass=19500, density=2880
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 5.661290322580645 != 6.77 within 2 places (1.1087096774193546 difference)
======================================================================
FAIL: test_positional_arguments_single_argument (test.TestFactory.test_positional_arguments_single_argument)
Test calling a factory using a sigle positional argument.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 80, in test_positional_arguments_single_argument
    self.assertIs(type(concrete2), solution.Concrete)
AssertionError: <class 'solution.Concrete'> is not <class 'solution.Concrete'>
----------------------------------------------------------------------
Ran 10 tests in 0.020s
FAILED (failures=4)
| f | 1 | class Material: | f | 1 | class Material: | 
| 2 | DENSITY = 0 | 2 | DENSITY = 0 | ||
| 3 | 3 | ||||
| 4 | def __init__(self, mass): | 4 | def __init__(self, mass): | ||
| 5 | self.mass = mass | 5 | self.mass = mass | ||
| 6 | 6 | ||||
| 7 | @property | 7 | @property | ||
| 8 | def volume(self): | 8 | def volume(self): | ||
| 9 | return float(self.mass / self.DENSITY) | 9 | return float(self.mass / self.DENSITY) | ||
| 10 | 10 | ||||
| 11 | 11 | ||||
| 12 | class Concrete(Material): | 12 | class Concrete(Material): | ||
| 13 | DENSITY = 2500 | 13 | DENSITY = 2500 | ||
| 14 | 14 | ||||
| 15 | 15 | ||||
| 16 | class Brick(Material): | 16 | class Brick(Material): | ||
| 17 | DENSITY = 2000 | 17 | DENSITY = 2000 | ||
| 18 | 18 | ||||
| 19 | 19 | ||||
| 20 | class Stone(Material): | 20 | class Stone(Material): | ||
| 21 | DENSITY = 1600 | 21 | DENSITY = 1600 | ||
| 22 | 22 | ||||
| 23 | 23 | ||||
| 24 | class Wood(Material): | 24 | class Wood(Material): | ||
| 25 | DENSITY = 600 | 25 | DENSITY = 600 | ||
| 26 | 26 | ||||
| 27 | 27 | ||||
| 28 | class Steel(Material): | 28 | class Steel(Material): | ||
| 29 | DENSITY = 7700 | 29 | DENSITY = 7700 | ||
| 30 | 30 | ||||
| 31 | 31 | ||||
| 32 | class Factory: | 32 | class Factory: | ||
| 33 | _MATERIALS = { | 33 | _MATERIALS = { | ||
| 34 | 'Concrete': Concrete, | 34 | 'Concrete': Concrete, | ||
| 35 | 'Brick': Brick, | 35 | 'Brick': Brick, | ||
| 36 | 'Stone': Stone, | 36 | 'Stone': Stone, | ||
| 37 | 'Wood': Wood, | 37 | 'Wood': Wood, | ||
| 38 | 'Steel': Steel | 38 | 'Steel': Steel | ||
| 39 | } | 39 | } | ||
| 40 | 40 | ||||
| 41 | _alloys = {} | 41 | _alloys = {} | ||
| 42 | _all_materials = set() | 42 | _all_materials = set() | ||
| 43 | _used_materials = set() | 43 | _used_materials = set() | ||
| 44 | 44 | ||||
| 45 | def __init__(self): | 45 | def __init__(self): | ||
| 46 | self._curr_materials = set() | 46 | self._curr_materials = set() | ||
| 47 | 47 | ||||
| 48 | def __call__(self, *args, **kwargs): | 48 | def __call__(self, *args, **kwargs): | ||
| 49 | if args and kwargs: | 49 | if args and kwargs: | ||
| 50 | raise ValueError('Function can be called with only positional or only keyword arguments') | 50 | raise ValueError('Function can be called with only positional or only keyword arguments') | ||
| 51 | elif not args and not kwargs: | 51 | elif not args and not kwargs: | ||
| 52 | raise ValueError('Function cannot be called without arguments') | 52 | raise ValueError('Function cannot be called without arguments') | ||
| 53 | elif kwargs: | 53 | elif kwargs: | ||
| 54 | return self._get_result_from_kwargs(**kwargs) | 54 | return self._get_result_from_kwargs(**kwargs) | ||
| 55 | elif args: | 55 | elif args: | ||
| 56 | return self._get_alloy_from_args(*args) | 56 | return self._get_alloy_from_args(*args) | ||
| 57 | 57 | ||||
| 58 | def _get_result_from_kwargs(self, **kwargs): | 58 | def _get_result_from_kwargs(self, **kwargs): | ||
| n | 59 | """Returns tuple of instances of materials, created from keyword arguments.""" | n | 59 | """Return tuple of instances of materials, created from keyword arguments.""" | 
| 60 | res = [] | 60 | res = [] | ||
| 61 | for key, val in kwargs.items(): | 61 | for key, val in kwargs.items(): | ||
| 62 | material = self._MATERIALS.get(key) | 62 | material = self._MATERIALS.get(key) | ||
| 63 | if material is None: | 63 | if material is None: | ||
| 64 | material = Factory._alloys.get(key) | 64 | material = Factory._alloys.get(key) | ||
| 65 | 65 | ||||
| 66 | if material is None or not issubclass(material, Material): | 66 | if material is None or not issubclass(material, Material): | ||
| 67 | raise ValueError('Invalid material') | 67 | raise ValueError('Invalid material') | ||
| 68 | 68 | ||||
| 69 | new_material = material(val) | 69 | new_material = material(val) | ||
| 70 | Factory._all_materials.add(new_material) | 70 | Factory._all_materials.add(new_material) | ||
| 71 | self._curr_materials.add(new_material) | 71 | self._curr_materials.add(new_material) | ||
| 72 | 72 | ||||
| 73 | res.append(new_material) | 73 | res.append(new_material) | ||
| 74 | 74 | ||||
| 75 | return tuple(res) | 75 | return tuple(res) | ||
| 76 | 76 | ||||
| 77 | def _get_average_density(self, *args): | 77 | def _get_average_density(self, *args): | ||
| n | 78 | """Returns the average density of the materials.""" | n | 78 | """Return the average density of the materials.""" | 
| 79 | density_sum = 0 | 79 | density_sum = 0 | ||
| 80 | for arg in args: | 80 | for arg in args: | ||
| 81 | density_sum += type(arg).DENSITY | 81 | density_sum += type(arg).DENSITY | ||
| 82 | 82 | ||||
| 83 | average_density = density_sum / len(args) | 83 | average_density = density_sum / len(args) | ||
| 84 | return average_density | 84 | return average_density | ||
| 85 | 85 | ||||
| 86 | def _get_mass(self, *args): | 86 | def _get_mass(self, *args): | ||
| n | 87 | """Calculates the sum of the masses of the materials""" | n | 87 | """Calculate the sum of the masses of the materials""" | 
| 88 | mass_sum = 0 | 88 | mass_sum = 0 | ||
| 89 | for arg in args: | 89 | for arg in args: | ||
| 90 | mass_sum += arg.mass | 90 | mass_sum += arg.mass | ||
| 91 | 91 | ||||
| 92 | return mass_sum | 92 | return mass_sum | ||
| 93 | 93 | ||||
| 94 | def _get_alloy_from_args(self, *args): | 94 | def _get_alloy_from_args(self, *args): | ||
| t | 95 | """Creates a dynamic class (alloy) from materials.""" | t | 95 | """Create a dynamic class (alloy) from materials.""" | 
| 96 | base_classes_names = [] | 96 | base_classes_names = [] | ||
| 97 | 97 | ||||
| 98 | for arg in args: | 98 | for arg in args: | ||
| 99 | if arg in Factory._used_materials: | 99 | if arg in Factory._used_materials: | ||
| 100 | raise AssertionError('Invalid material') | 100 | raise AssertionError('Invalid material') | ||
| 101 | 101 | ||||
| 102 | Factory._used_materials.add(arg) | 102 | Factory._used_materials.add(arg) | ||
| 103 | 103 | ||||
| 104 | base_classes_names.extend(type(arg).__name__.split('_')) | 104 | base_classes_names.extend(type(arg).__name__.split('_')) | ||
| 105 | 105 | ||||
| 106 | sorted_materials_names = sorted(base_classes_names) | 106 | sorted_materials_names = sorted(base_classes_names) | ||
| 107 | new_alloy_name = '_'.join(sorted_materials_names) | 107 | new_alloy_name = '_'.join(sorted_materials_names) | ||
| 108 | 108 | ||||
| 109 | if new_alloy_name not in Factory._alloys: | 109 | if new_alloy_name not in Factory._alloys: | ||
| 110 | average_density = self._get_average_density(*args) | 110 | average_density = self._get_average_density(*args) | ||
| 111 | alloy = type( | 111 | alloy = type( | ||
| 112 | new_alloy_name, | 112 | new_alloy_name, | ||
| 113 | (Material,), | 113 | (Material,), | ||
| 114 | {'DENSITY': average_density} | 114 | {'DENSITY': average_density} | ||
| 115 | ) | 115 | ) | ||
| 116 | Factory._alloys[new_alloy_name] = alloy | 116 | Factory._alloys[new_alloy_name] = alloy | ||
| 117 | 117 | ||||
| 118 | alloy = Factory._alloys[new_alloy_name] | 118 | alloy = Factory._alloys[new_alloy_name] | ||
| 119 | alloy_mass = self._get_mass(*args) | 119 | alloy_mass = self._get_mass(*args) | ||
| 120 | 120 | ||||
| 121 | new_alloy = alloy(alloy_mass) | 121 | new_alloy = alloy(alloy_mass) | ||
| 122 | self._curr_materials.add(new_alloy) | 122 | self._curr_materials.add(new_alloy) | ||
| 123 | Factory._all_materials.add(new_alloy) | 123 | Factory._all_materials.add(new_alloy) | ||
| 124 | return new_alloy | 124 | return new_alloy | ||
| 125 | 125 | ||||
| 126 | def can_build(self, needed_volume): | 126 | def can_build(self, needed_volume): | ||
| 127 | volume_sum = 0 | 127 | volume_sum = 0 | ||
| 128 | for material in self._curr_materials: | 128 | for material in self._curr_materials: | ||
| 129 | if material not in Factory._used_materials: | 129 | if material not in Factory._used_materials: | ||
| 130 | volume_sum += material.volume | 130 | volume_sum += material.volume | ||
| 131 | 131 | ||||
| 132 | return volume_sum >= needed_volume | 132 | return volume_sum >= needed_volume | ||
| 133 | 133 | ||||
| 134 | @classmethod | 134 | @classmethod | ||
| 135 | def can_build_together(cls, needed_volume): | 135 | def can_build_together(cls, needed_volume): | ||
| 136 | volume_sum = 0 | 136 | volume_sum = 0 | ||
| 137 | for material in cls._all_materials: | 137 | for material in cls._all_materials: | ||
| 138 | volume_sum += material.volume | 138 | volume_sum += material.volume | ||
| 139 | 139 | ||||
| 140 | return volume_sum >= needed_volume | 140 | return volume_sum >= needed_volume | 
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| 
 | 
 | |||||||||
26.11.2024 14:22
26.11.2024 14:25
26.11.2024 14:23