1class Material:
2 DENSITIES = {"Concrete": 2500, "Brick": 2000, "Stone": 1600, "Wood": 600, "Steel": 7700}
3
4 def __init__(self, mass):
5 if not isinstance(mass, int):
6 raise TypeError("Mass should be an integer.")
7 self.mass = mass
8 self.used = False
9 self.components = [type(self).__name__]
10
11 @property
12 def volume(self):
13 return float(self.mass / self.density)
14
15
16class Concrete(Material):
17 density = Material.DENSITIES["Concrete"]
18
19
20class Brick(Material):
21 density = Material.DENSITIES["Brick"]
22
23
24class Stone(Material):
25 density = Material.DENSITIES["Stone"]
26
27
28class Wood(Material):
29 density = Material.DENSITIES["Wood"]
30
31
32class Steel(Material):
33 density = Material.DENSITIES["Steel"]
34
35
36class Factory:
37 ACCESS_CLASS = {"Concrete": Concrete, "Brick": Brick,
38 "Stone": Stone, "Wood": Wood, "Steel": Steel}
39 dynamic_classes = {}
40 all_materials = []
41
42 def __init__(self):
43 self.materials = []
44
45 def __call__(self, *args, **kwargs):
46 if (args and kwargs) or (not args and not kwargs):
47 raise ValueError("Input error for factory arguments.")
48 if kwargs:
49 return self.create_materials_from_kwargs(kwargs)
50 elif args:
51 return self.create_alloy_from_args(args)
52
53 def create_materials_from_kwargs(self, kwargs):
54 instance = []
55 for class_name, mass in kwargs.items():
56 if class_name not in self.ACCESS_CLASS:
57 raise ValueError("Invalid material name.")
58 cls = self.ACCESS_CLASS[class_name]
59 material = cls(mass)
60 instance.append(material)
61 self.materials.append(material)
62 self.all_materials.append(material)
63 return tuple(instance)
64
65 def create_alloy_from_args(self, args):
66 if any(arg.used for arg in args) or any(not isinstance(arg, Material) for arg in args):
67 raise AssertionError("Materials have already been used or are invalid.")
68
69 used_materials = []
70 for arg in args:
71 if isinstance(arg, Material):
72 for component in arg.components:
73 if "_" in component:
74 used_materials.extend(component.split("_"))
75 else:
76 used_materials.append(component)
77 else:
78 raise ValueError("Invalid argument type.")
79
80 class_name = "_".join(sorted(set(used_materials)))
81 mass = sum(arg.mass for arg in args)
82
83 if class_name not in self.dynamic_classes:
84 all_densities = [self.ACCESS_CLASS[comp].density if comp in self.ACCESS_CLASS else self.dynamic_classes[comp].density
85 for comp in set(used_materials)]
86 average_density = sum(all_densities) / len(all_densities)
87 dynamic_class = type(class_name, (Material,), {'density': average_density})
88 self.dynamic_classes[class_name] = dynamic_class
89 else:
90 dynamic_class = self.dynamic_classes[class_name]
91
92 new_material = dynamic_class(mass)
93 self.materials.append(new_material)
94 self.all_materials.append(new_material)
95
96 for arg in args:
97 arg.used = True
98
99 return new_material
100
101 def can_build(self, required_volume):
102 return sum(material.volume for material in self.materials if not material.used) >= required_volume
103
104 @classmethod
105 def can_build_together(cls, required_volume):
106 return sum(material.volume for material in cls.all_materials if not material.used) >= required_volume
..E.E..F..
======================================================================
ERROR: test_materials_between_factories (test.TestFactory.test_materials_between_factories)
Test materials sharing.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 156, in test_materials_between_factories
result2, = self.factory2(Brick_Concrete_Steel_Stone_Wood=2)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 49, in __call__
return self.create_materials_from_kwargs(kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 57, in create_materials_from_kwargs
raise ValueError("Invalid material name.")
ValueError: Invalid material name.
======================================================================
ERROR: test_named_arguments_with_dynamically_created_classes (test.TestFactory.test_named_arguments_with_dynamically_created_classes)
Test dynamically created classes uniqueness.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 145, in test_named_arguments_with_dynamically_created_classes
result2, = self.factory1(Brick_Concrete_Steel_Stone_Wood=2)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 49, in __call__
return self.create_materials_from_kwargs(kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 57, in create_materials_from_kwargs
raise ValueError("Invalid material name.")
ValueError: Invalid material name.
======================================================================
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=1, errors=2)
f | 1 | class Material: | f | 1 | class Material: |
2 | DENSITIES = {"Concrete": 2500, "Brick": 2000, "Stone": 1600, "Wood": 600, "Steel": 7700} | 2 | DENSITIES = {"Concrete": 2500, "Brick": 2000, "Stone": 1600, "Wood": 600, "Steel": 7700} | ||
3 | 3 | ||||
4 | def __init__(self, mass): | 4 | def __init__(self, mass): | ||
5 | if not isinstance(mass, int): | 5 | if not isinstance(mass, int): | ||
6 | raise TypeError("Mass should be an integer.") | 6 | raise TypeError("Mass should be an integer.") | ||
7 | self.mass = mass | 7 | self.mass = mass | ||
8 | self.used = False | 8 | self.used = False | ||
9 | self.components = [type(self).__name__] | 9 | self.components = [type(self).__name__] | ||
10 | 10 | ||||
11 | @property | 11 | @property | ||
12 | def volume(self): | 12 | def volume(self): | ||
13 | return float(self.mass / self.density) | 13 | return float(self.mass / self.density) | ||
14 | 14 | ||||
15 | 15 | ||||
16 | class Concrete(Material): | 16 | class Concrete(Material): | ||
17 | density = Material.DENSITIES["Concrete"] | 17 | density = Material.DENSITIES["Concrete"] | ||
18 | 18 | ||||
19 | 19 | ||||
20 | class Brick(Material): | 20 | class Brick(Material): | ||
21 | density = Material.DENSITIES["Brick"] | 21 | density = Material.DENSITIES["Brick"] | ||
22 | 22 | ||||
23 | 23 | ||||
24 | class Stone(Material): | 24 | class Stone(Material): | ||
25 | density = Material.DENSITIES["Stone"] | 25 | density = Material.DENSITIES["Stone"] | ||
26 | 26 | ||||
27 | 27 | ||||
28 | class Wood(Material): | 28 | class Wood(Material): | ||
29 | density = Material.DENSITIES["Wood"] | 29 | density = Material.DENSITIES["Wood"] | ||
30 | 30 | ||||
31 | 31 | ||||
32 | class Steel(Material): | 32 | class Steel(Material): | ||
33 | density = Material.DENSITIES["Steel"] | 33 | density = Material.DENSITIES["Steel"] | ||
34 | 34 | ||||
35 | 35 | ||||
36 | class Factory: | 36 | class Factory: | ||
37 | ACCESS_CLASS = {"Concrete": Concrete, "Brick": Brick, | 37 | ACCESS_CLASS = {"Concrete": Concrete, "Brick": Brick, | ||
38 | "Stone": Stone, "Wood": Wood, "Steel": Steel} | 38 | "Stone": Stone, "Wood": Wood, "Steel": Steel} | ||
39 | dynamic_classes = {} | 39 | dynamic_classes = {} | ||
40 | all_materials = [] | 40 | all_materials = [] | ||
41 | 41 | ||||
42 | def __init__(self): | 42 | def __init__(self): | ||
43 | self.materials = [] | 43 | self.materials = [] | ||
44 | 44 | ||||
45 | def __call__(self, *args, **kwargs): | 45 | def __call__(self, *args, **kwargs): | ||
46 | if (args and kwargs) or (not args and not kwargs): | 46 | if (args and kwargs) or (not args and not kwargs): | ||
47 | raise ValueError("Input error for factory arguments.") | 47 | raise ValueError("Input error for factory arguments.") | ||
48 | if kwargs: | 48 | if kwargs: | ||
49 | return self.create_materials_from_kwargs(kwargs) | 49 | return self.create_materials_from_kwargs(kwargs) | ||
50 | elif args: | 50 | elif args: | ||
51 | return self.create_alloy_from_args(args) | 51 | return self.create_alloy_from_args(args) | ||
52 | 52 | ||||
53 | def create_materials_from_kwargs(self, kwargs): | 53 | def create_materials_from_kwargs(self, kwargs): | ||
54 | instance = [] | 54 | instance = [] | ||
55 | for class_name, mass in kwargs.items(): | 55 | for class_name, mass in kwargs.items(): | ||
56 | if class_name not in self.ACCESS_CLASS: | 56 | if class_name not in self.ACCESS_CLASS: | ||
57 | raise ValueError("Invalid material name.") | 57 | raise ValueError("Invalid material name.") | ||
58 | cls = self.ACCESS_CLASS[class_name] | 58 | cls = self.ACCESS_CLASS[class_name] | ||
59 | material = cls(mass) | 59 | material = cls(mass) | ||
60 | instance.append(material) | 60 | instance.append(material) | ||
61 | self.materials.append(material) | 61 | self.materials.append(material) | ||
62 | self.all_materials.append(material) | 62 | self.all_materials.append(material) | ||
63 | return tuple(instance) | 63 | return tuple(instance) | ||
64 | 64 | ||||
65 | def create_alloy_from_args(self, args): | 65 | def create_alloy_from_args(self, args): | ||
66 | if any(arg.used for arg in args) or any(not isinstance(arg, Material) for arg in args): | 66 | if any(arg.used for arg in args) or any(not isinstance(arg, Material) for arg in args): | ||
67 | raise AssertionError("Materials have already been used or are invalid.") | 67 | raise AssertionError("Materials have already been used or are invalid.") | ||
68 | 68 | ||||
69 | used_materials = [] | 69 | used_materials = [] | ||
70 | for arg in args: | 70 | for arg in args: | ||
71 | if isinstance(arg, Material): | 71 | if isinstance(arg, Material): | ||
72 | for component in arg.components: | 72 | for component in arg.components: | ||
73 | if "_" in component: | 73 | if "_" in component: | ||
74 | used_materials.extend(component.split("_")) | 74 | used_materials.extend(component.split("_")) | ||
75 | else: | 75 | else: | ||
76 | used_materials.append(component) | 76 | used_materials.append(component) | ||
77 | else: | 77 | else: | ||
78 | raise ValueError("Invalid argument type.") | 78 | raise ValueError("Invalid argument type.") | ||
79 | 79 | ||||
80 | class_name = "_".join(sorted(set(used_materials))) | 80 | class_name = "_".join(sorted(set(used_materials))) | ||
81 | mass = sum(arg.mass for arg in args) | 81 | mass = sum(arg.mass for arg in args) | ||
82 | 82 | ||||
83 | if class_name not in self.dynamic_classes: | 83 | if class_name not in self.dynamic_classes: | ||
84 | all_densities = [self.ACCESS_CLASS[comp].density if comp in self.ACCESS_CLASS else self.dynamic_classes[comp].density | 84 | all_densities = [self.ACCESS_CLASS[comp].density if comp in self.ACCESS_CLASS else self.dynamic_classes[comp].density | ||
85 | for comp in set(used_materials)] | 85 | for comp in set(used_materials)] | ||
86 | average_density = sum(all_densities) / len(all_densities) | 86 | average_density = sum(all_densities) / len(all_densities) | ||
87 | dynamic_class = type(class_name, (Material,), {'density': average_density}) | 87 | dynamic_class = type(class_name, (Material,), {'density': average_density}) | ||
88 | self.dynamic_classes[class_name] = dynamic_class | 88 | self.dynamic_classes[class_name] = dynamic_class | ||
89 | else: | 89 | else: | ||
90 | dynamic_class = self.dynamic_classes[class_name] | 90 | dynamic_class = self.dynamic_classes[class_name] | ||
91 | 91 | ||||
92 | new_material = dynamic_class(mass) | 92 | new_material = dynamic_class(mass) | ||
93 | self.materials.append(new_material) | 93 | self.materials.append(new_material) | ||
94 | self.all_materials.append(new_material) | 94 | self.all_materials.append(new_material) | ||
95 | 95 | ||||
96 | for arg in args: | 96 | for arg in args: | ||
97 | arg.used = True | 97 | arg.used = True | ||
98 | 98 | ||||
99 | return new_material | 99 | return new_material | ||
100 | 100 | ||||
101 | def can_build(self, required_volume): | 101 | def can_build(self, required_volume): | ||
n | 102 | total_volume = sum(material.volume for material in self.materials if not material.used) | n | 102 | return sum(material.volume for material in self.materials if not material.used) >= required_volume |
103 | return total_volume >= required_volume | ||||
104 | 103 | ||||
105 | @classmethod | 104 | @classmethod | ||
106 | def can_build_together(cls, required_volume): | 105 | def can_build_together(cls, required_volume): | ||
t | 107 | total_volume = sum(material.volume for material in cls.all_materials if not material.used) | t | 106 | return sum(material.volume for material in cls.all_materials if not material.used) >= required_volume |
108 | return total_volume >= required_volume |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
n | 1 | class Mixin: | n | 1 | class Material: |
2 | DENSITIES = {"Concrete" : 2500, "Brick" : 2000, "Stone" : 1600, "Wood" : 600, "Steel" : 7700} | 2 | DENSITIES = {"Concrete": 2500, "Brick": 2000, "Stone": 1600, "Wood": 600, "Steel": 7700} | ||
3 | 3 | ||||
n | 4 | def __init__(self, mass, density): | n | 4 | def __init__(self, mass): |
5 | if not isinstance(mass, int): | 5 | if not isinstance(mass, int): | ||
6 | raise TypeError("Mass should be an integer.") | 6 | raise TypeError("Mass should be an integer.") | ||
n | 7 | n | |||
8 | self.mass = mass | 7 | self.mass = mass | ||
n | 9 | self.density = density | n | ||
10 | self.used = False | 8 | self.used = False | ||
11 | self.components = [type(self).__name__] | 9 | self.components = [type(self).__name__] | ||
12 | 10 | ||||
13 | @property | 11 | @property | ||
14 | def volume(self): | 12 | def volume(self): | ||
15 | return float(self.mass / self.density) | 13 | return float(self.mass / self.density) | ||
16 | 14 | ||||
17 | 15 | ||||
n | 18 | class Concrete(Mixin): | n | 16 | class Concrete(Material): |
19 | def __init__(self, mass): | 17 | density = Material.DENSITIES["Concrete"] | ||
20 | super().__init__(mass, self.DENSITIES["Concrete"]) | ||||
21 | 18 | ||||
22 | 19 | ||||
n | 23 | class Brick(Mixin): | n | 20 | class Brick(Material): |
24 | def __init__(self, mass): | 21 | density = Material.DENSITIES["Brick"] | ||
25 | super().__init__(mass, self.DENSITIES["Brick"]) | ||||
26 | 22 | ||||
27 | 23 | ||||
n | 28 | class Stone(Mixin): | n | 24 | class Stone(Material): |
29 | def __init__(self, mass): | 25 | density = Material.DENSITIES["Stone"] | ||
30 | super().__init__(mass, self.DENSITIES["Stone"]) | ||||
31 | 26 | ||||
32 | 27 | ||||
n | 33 | class Wood(Mixin): | n | 28 | class Wood(Material): |
34 | def __init__(self, mass): | 29 | density = Material.DENSITIES["Wood"] | ||
35 | super().__init__(mass, self.DENSITIES["Wood"]) | ||||
36 | 30 | ||||
37 | 31 | ||||
n | 38 | class Steel(Mixin): | n | 32 | class Steel(Material): |
39 | def __init__(self, mass): | 33 | density = Material.DENSITIES["Steel"] | ||
40 | super().__init__(mass, self.DENSITIES["Steel"]) | ||||
41 | 34 | ||||
42 | 35 | ||||
43 | class Factory: | 36 | class Factory: | ||
n | 44 | ACCESS_CLASS = {"Concrete" : Concrete, "Brick" : Brick, | n | 37 | ACCESS_CLASS = {"Concrete": Concrete, "Brick": Brick, |
45 | "Stone" : Stone, "Wood" : Wood, "Steel" : Steel} | 38 | "Stone": Stone, "Wood": Wood, "Steel": Steel} | ||
46 | dynamic_classes = {} | 39 | dynamic_classes = {} | ||
47 | all_materials = [] | 40 | all_materials = [] | ||
48 | 41 | ||||
49 | def __init__(self): | 42 | def __init__(self): | ||
50 | self.materials = [] | 43 | self.materials = [] | ||
51 | 44 | ||||
52 | def __call__(self, *args, **kwargs): | 45 | def __call__(self, *args, **kwargs): | ||
53 | if (args and kwargs) or (not args and not kwargs): | 46 | if (args and kwargs) or (not args and not kwargs): | ||
n | 54 | raise ValueError("Input error for factory argements.") | n | 47 | raise ValueError("Input error for factory arguments.") |
55 | if kwargs: | 48 | if kwargs: | ||
56 | return self.create_materials_from_kwargs(kwargs) | 49 | return self.create_materials_from_kwargs(kwargs) | ||
57 | elif args: | 50 | elif args: | ||
58 | return self.create_alloy_from_args(args) | 51 | return self.create_alloy_from_args(args) | ||
59 | 52 | ||||
60 | def create_materials_from_kwargs(self, kwargs): | 53 | def create_materials_from_kwargs(self, kwargs): | ||
61 | instance = [] | 54 | instance = [] | ||
62 | for class_name, mass in kwargs.items(): | 55 | for class_name, mass in kwargs.items(): | ||
63 | if class_name not in self.ACCESS_CLASS: | 56 | if class_name not in self.ACCESS_CLASS: | ||
64 | raise ValueError("Invalid material name.") | 57 | raise ValueError("Invalid material name.") | ||
n | 65 | cls = self.ACCESS_CLASS.get(class_name) or self.dynamic_classes[class_name] | n | 58 | cls = self.ACCESS_CLASS[class_name] |
66 | if not cls: | 59 | material = cls(mass) | ||
67 | raise ValueError("Invalid material name.") | ||||
68 | instance.append(cls(mass)) | 60 | instance.append(material) | ||
61 | self.materials.append(material) | ||||
69 | self.all_materials.append(cls(mass)) | 62 | self.all_materials.append(material) | ||
70 | return tuple(instance) | 63 | return tuple(instance) | ||
71 | 64 | ||||
72 | def create_alloy_from_args(self, args): | 65 | def create_alloy_from_args(self, args): | ||
n | 73 | if any(arg.used for arg in args) or any(not isinstance(arg, Mixin) for arg in args): | n | 66 | if any(arg.used for arg in args) or any(not isinstance(arg, Material) for arg in args): |
74 | raise AssertionError("Materials have already been used.") | 67 | raise AssertionError("Materials have already been used or are invalid.") | ||
75 | 68 | ||||
76 | used_materials = [] | 69 | used_materials = [] | ||
77 | for arg in args: | 70 | for arg in args: | ||
n | 78 | if isinstance(arg, Mixin): | n | 71 | if isinstance(arg, Material): |
79 | for component in arg.components: | 72 | for component in arg.components: | ||
80 | if "_" in component: | 73 | if "_" in component: | ||
81 | used_materials.extend(component.split("_")) | 74 | used_materials.extend(component.split("_")) | ||
82 | else: | 75 | else: | ||
83 | used_materials.append(component) | 76 | used_materials.append(component) | ||
84 | else: | 77 | else: | ||
85 | raise ValueError("Invalid argument type.") | 78 | raise ValueError("Invalid argument type.") | ||
n | 86 | n | 79 | ||
87 | class_name = "_".join(sorted(set(used_materials))) | 80 | class_name = "_".join(sorted(set(used_materials))) | ||
88 | mass = sum(arg.mass for arg in args) | 81 | mass = sum(arg.mass for arg in args) | ||
n | 89 | average_density = sum(arg.density for arg in args) / len(args) | n | ||
90 | 82 | ||||
91 | if class_name not in self.dynamic_classes: | 83 | if class_name not in self.dynamic_classes: | ||
n | n | 84 | all_densities = [self.ACCESS_CLASS[comp].density if comp in self.ACCESS_CLASS else self.dynamic_classes[comp].density | ||
85 | for comp in set(used_materials)] | ||||
86 | average_density = sum(all_densities) / len(all_densities) | ||||
92 | dynamic_class = type(class_name, (Mixin,), {'density': average_density}) | 87 | dynamic_class = type(class_name, (Material,), {'density': average_density}) | ||
93 | self.dynamic_classes[class_name] = dynamic_class | 88 | self.dynamic_classes[class_name] = dynamic_class | ||
94 | else: | 89 | else: | ||
95 | dynamic_class = self.dynamic_classes[class_name] | 90 | dynamic_class = self.dynamic_classes[class_name] | ||
96 | 91 | ||||
n | 97 | new_material = dynamic_class(mass, average_density) | n | 92 | new_material = dynamic_class(mass) |
98 | self.materials.append(new_material) | 93 | self.materials.append(new_material) | ||
99 | self.all_materials.append(new_material) | 94 | self.all_materials.append(new_material) | ||
100 | 95 | ||||
101 | for arg in args: | 96 | for arg in args: | ||
102 | arg.used = True | 97 | arg.used = True | ||
103 | 98 | ||||
104 | return new_material | 99 | return new_material | ||
105 | 100 | ||||
106 | def can_build(self, required_volume): | 101 | def can_build(self, required_volume): | ||
n | 107 | return sum(cur_material.volume for cur_material in self.materials) >= required_volume | n | 102 | total_volume = sum(material.volume for material in self.materials if not material.used) |
103 | return total_volume >= required_volume | ||||
108 | 104 | ||||
109 | @classmethod | 105 | @classmethod | ||
110 | def can_build_together(cls, required_volume): | 106 | def can_build_together(cls, required_volume): | ||
t | 111 | return sum(cur_material.volume for cur_material in cls.all_materials if not cur_material.used) >= required_volume | t | 107 | total_volume = sum(material.volume for material in cls.all_materials if not material.used) |
108 | return total_volume >= required_volume |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|