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