1class Material:
2 THICKNESS = 0
3 def __init__(self, mass: int):
4 self.mass = mass
5
6 @property
7 def volume(self):
8 return float(self.mass / self.THICKNESS)
9
10
11class Concrete(Material):
12 THICKNESS = 2500
13
14
15class Brick(Material):
16 THICKNESS = 2000
17
18
19class Stone(Material):
20 THICKNESS = 1600
21
22
23class Wood(Material):
24 THICKNESS = 600
25
26
27class Steel(Material):
28 THICKNESS = 7700
29
30
31class Factory:
32 _dynamic_classes = {}
33 all_created_materials = set()
34 used_materials = set()
35
36 def __init__(self):
37 self.current_materials = set()
38
39 def __call__(self, *args, **kwargs):
40 if args and kwargs:
41 raise ValueError("Cannot mix positional and keyword arguments.")
42 if not args and not kwargs:
43 raise ValueError("Cannot call the function without arguments.")
44
45 if args:
46 return self._call_with_positional_args(args)
47 if kwargs:
48 return self._call_with_keyword_args(kwargs)
49
50 def _call_with_keyword_args(self, kwargs):
51 ALLOWED_NAMES = {
52 "Concrete": Concrete,
53 "Brick": Brick,
54 "Stone": Stone,
55 "Wood": Wood,
56 "Steel": Steel,
57 }
58
59 result = []
60 for name, mass in kwargs.items():
61 if name not in ALLOWED_NAMES and name not in Factory._dynamic_classes:
62 raise ValueError(f"Invalid material type: {name}")
63 material_class = (
64 ALLOWED_NAMES.get(name) or Factory._dynamic_classes[name]
65 )
66 material = material_class(mass)
67 self.current_materials.add(material)
68 Factory.all_created_materials.add(material)
69 result.append(material)
70
71 return tuple(result)
72
73 def _call_with_positional_args(self, args):
74 for material in args:
75 if material in Factory.used_materials:
76 raise AssertionError("This material has already been used.")
77 if not isinstance(material, Material):
78 raise ValueError("Positional arguments must be instances of Materials.")
79 Factory.used_materials.add(material)
80
81 base_classes_names = []
82 for arg in args:
83 base_classes_names += type(arg).__name__.split('_')
84
85 sorted_materials_names = sorted(base_classes_names)
86 new_class_name = '_'.join(sorted_materials_names)
87
88 if new_class_name not in Factory._dynamic_classes:
89 avg_thickness = sum(material.THICKNESS for material in args) / len(args)
90 new_class = type(new_class_name, (Material,), {"THICKNESS": avg_thickness})
91 Factory._dynamic_classes[new_class_name] = new_class
92
93 new_class = Factory._dynamic_classes[new_class_name]
94 total_mass = sum(material.mass for material in args)
95 new_material = new_class(total_mass)
96
97 self.current_materials.add(new_material)
98 Factory.all_created_materials.add(new_material)
99 return new_material
100
101 def can_build(self, necessary_volume: int):
102 total_volume = sum(
103 instance.volume
104 for instance in self.current_materials
105 if instance not in Factory.used_materials
106 )
107 return total_volume >= necessary_volume
108
109 @classmethod
110 def can_build_together(cls, necessary_volume: int):
111 total_volume = sum(
112 instance.volume
113 for instance in cls.all_created_materials
114 if instance not in cls.used_materials or isinstance(instance, Material)
115 )
116 return total_volume >= necessary_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 46, in __call__
return self._call_with_positional_args(args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 76, in _call_with_positional_args
raise AssertionError("This material has already been used.")
AssertionError: This material has already been used.
======================================================================
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.011s
FAILED (failures=4)
n | 1 | class Materials: | n | 1 | class Material: |
2 | THICKNESS = 0 | ||||
2 | def __init__(self, mass: int): | 3 | def __init__(self, mass: int): | ||
3 | self.mass = mass | 4 | self.mass = mass | ||
4 | 5 | ||||
5 | @property | 6 | @property | ||
6 | def volume(self): | 7 | def volume(self): | ||
n | 7 | return float(self.mass / self.thickness) | n | 8 | return float(self.mass / self.THICKNESS) |
8 | 9 | ||||
9 | 10 | ||||
n | 10 | class Concrete(Materials): | n | 11 | class Concrete(Material): |
11 | thickness = 2500 | 12 | THICKNESS = 2500 | ||
12 | 13 | ||||
13 | 14 | ||||
n | 14 | class Brick(Materials): | n | 15 | class Brick(Material): |
15 | thickness = 2000 | 16 | THICKNESS = 2000 | ||
16 | 17 | ||||
17 | 18 | ||||
n | 18 | class Stone(Materials): | n | 19 | class Stone(Material): |
19 | thickness = 1600 | 20 | THICKNESS = 1600 | ||
20 | 21 | ||||
21 | 22 | ||||
n | 22 | class Wood(Materials): | n | 23 | class Wood(Material): |
23 | thickness = 600 | 24 | THICKNESS = 600 | ||
24 | 25 | ||||
25 | 26 | ||||
n | 26 | class Steel(Materials): | n | 27 | class Steel(Material): |
27 | thickness = 7700 | 28 | THICKNESS = 7700 | ||
28 | 29 | ||||
29 | 30 | ||||
30 | class Factory: | 31 | class Factory: | ||
31 | _dynamic_classes = {} | 32 | _dynamic_classes = {} | ||
32 | all_created_materials = set() | 33 | all_created_materials = set() | ||
33 | used_materials = set() | 34 | used_materials = set() | ||
34 | 35 | ||||
35 | def __init__(self): | 36 | def __init__(self): | ||
36 | self.current_materials = set() | 37 | self.current_materials = set() | ||
37 | 38 | ||||
38 | def __call__(self, *args, **kwargs): | 39 | def __call__(self, *args, **kwargs): | ||
39 | if args and kwargs: | 40 | if args and kwargs: | ||
40 | raise ValueError("Cannot mix positional and keyword arguments.") | 41 | raise ValueError("Cannot mix positional and keyword arguments.") | ||
41 | if not args and not kwargs: | 42 | if not args and not kwargs: | ||
42 | raise ValueError("Cannot call the function without arguments.") | 43 | raise ValueError("Cannot call the function without arguments.") | ||
43 | 44 | ||||
44 | if args: | 45 | if args: | ||
45 | return self._call_with_positional_args(args) | 46 | return self._call_with_positional_args(args) | ||
46 | if kwargs: | 47 | if kwargs: | ||
47 | return self._call_with_keyword_args(kwargs) | 48 | return self._call_with_keyword_args(kwargs) | ||
48 | 49 | ||||
49 | def _call_with_keyword_args(self, kwargs): | 50 | def _call_with_keyword_args(self, kwargs): | ||
n | 50 | allowed_names = { | n | 51 | ALLOWED_NAMES = { |
51 | "Concrete": Concrete, | 52 | "Concrete": Concrete, | ||
52 | "Brick": Brick, | 53 | "Brick": Brick, | ||
53 | "Stone": Stone, | 54 | "Stone": Stone, | ||
54 | "Wood": Wood, | 55 | "Wood": Wood, | ||
55 | "Steel": Steel, | 56 | "Steel": Steel, | ||
56 | } | 57 | } | ||
57 | 58 | ||||
58 | result = [] | 59 | result = [] | ||
59 | for name, mass in kwargs.items(): | 60 | for name, mass in kwargs.items(): | ||
n | 60 | if name not in allowed_names and name not in Factory._dynamic_classes: | n | 61 | if name not in ALLOWED_NAMES and name not in Factory._dynamic_classes: |
61 | raise ValueError(f"Invalid material type: {name}") | 62 | raise ValueError(f"Invalid material type: {name}") | ||
62 | material_class = ( | 63 | material_class = ( | ||
n | 63 | allowed_names.get(name) or Factory._dynamic_classes[name] | n | 64 | ALLOWED_NAMES.get(name) or Factory._dynamic_classes[name] |
64 | ) | 65 | ) | ||
65 | material = material_class(mass) | 66 | material = material_class(mass) | ||
66 | self.current_materials.add(material) | 67 | self.current_materials.add(material) | ||
67 | Factory.all_created_materials.add(material) | 68 | Factory.all_created_materials.add(material) | ||
68 | result.append(material) | 69 | result.append(material) | ||
69 | 70 | ||||
70 | return tuple(result) | 71 | return tuple(result) | ||
71 | 72 | ||||
72 | def _call_with_positional_args(self, args): | 73 | def _call_with_positional_args(self, args): | ||
73 | for material in args: | 74 | for material in args: | ||
74 | if material in Factory.used_materials: | 75 | if material in Factory.used_materials: | ||
75 | raise AssertionError("This material has already been used.") | 76 | raise AssertionError("This material has already been used.") | ||
n | 76 | if not isinstance(material, Materials): | n | 77 | if not isinstance(material, Material): |
77 | raise ValueError("Positional arguments must be instances of Materials.") | 78 | raise ValueError("Positional arguments must be instances of Materials.") | ||
78 | Factory.used_materials.add(material) | 79 | Factory.used_materials.add(material) | ||
79 | 80 | ||||
80 | base_classes_names = [] | 81 | base_classes_names = [] | ||
81 | for arg in args: | 82 | for arg in args: | ||
82 | base_classes_names += type(arg).__name__.split('_') | 83 | base_classes_names += type(arg).__name__.split('_') | ||
83 | 84 | ||||
84 | sorted_materials_names = sorted(base_classes_names) | 85 | sorted_materials_names = sorted(base_classes_names) | ||
85 | new_class_name = '_'.join(sorted_materials_names) | 86 | new_class_name = '_'.join(sorted_materials_names) | ||
86 | 87 | ||||
87 | if new_class_name not in Factory._dynamic_classes: | 88 | if new_class_name not in Factory._dynamic_classes: | ||
n | 88 | avg_thickness = sum(material.thickness for material in args) / len(args) | n | 89 | avg_thickness = sum(material.THICKNESS for material in args) / len(args) |
89 | new_class = type(new_class_name, (Materials,), {"thickness": avg_thickness}) | 90 | new_class = type(new_class_name, (Material,), {"THICKNESS": avg_thickness}) | ||
90 | Factory._dynamic_classes[new_class_name] = new_class | 91 | Factory._dynamic_classes[new_class_name] = new_class | ||
91 | 92 | ||||
92 | new_class = Factory._dynamic_classes[new_class_name] | 93 | new_class = Factory._dynamic_classes[new_class_name] | ||
93 | total_mass = sum(material.mass for material in args) | 94 | total_mass = sum(material.mass for material in args) | ||
94 | new_material = new_class(total_mass) | 95 | new_material = new_class(total_mass) | ||
95 | 96 | ||||
96 | self.current_materials.add(new_material) | 97 | self.current_materials.add(new_material) | ||
97 | Factory.all_created_materials.add(new_material) | 98 | Factory.all_created_materials.add(new_material) | ||
98 | return new_material | 99 | return new_material | ||
99 | 100 | ||||
100 | def can_build(self, necessary_volume: int): | 101 | def can_build(self, necessary_volume: int): | ||
101 | total_volume = sum( | 102 | total_volume = sum( | ||
102 | instance.volume | 103 | instance.volume | ||
103 | for instance in self.current_materials | 104 | for instance in self.current_materials | ||
104 | if instance not in Factory.used_materials | 105 | if instance not in Factory.used_materials | ||
105 | ) | 106 | ) | ||
106 | return total_volume >= necessary_volume | 107 | return total_volume >= necessary_volume | ||
107 | 108 | ||||
108 | @classmethod | 109 | @classmethod | ||
109 | def can_build_together(cls, necessary_volume: int): | 110 | def can_build_together(cls, necessary_volume: int): | ||
110 | total_volume = sum( | 111 | total_volume = sum( | ||
111 | instance.volume | 112 | instance.volume | ||
112 | for instance in cls.all_created_materials | 113 | for instance in cls.all_created_materials | ||
t | 113 | if instance not in cls.used_materials or isinstance(instance, Materials) | t | 114 | if instance not in cls.used_materials or isinstance(instance, Material) |
114 | ) | 115 | ) | ||
115 | return total_volume >= necessary_volume | 116 | return total_volume >= necessary_volume |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|