1class Material:
2 """Base material class."""
3 density = 1
4
5 def __init__(self, weight):
6 self.weight = weight
7
8 @property
9 def volume(self):
10 """Calculate material's volume."""
11 return self.weight / self.density
12
13
14class Concrete(Material):
15 density = 2500
16
17
18class Brick(Material):
19 density = 2000
20
21
22class Stone(Material):
23 density = 1600
24
25
26class Wood(Material):
27 density = 600
28
29
30class Steel(Material):
31 density = 7700
32
33
34class Factory:
35 """Create (new) materials."""
36 # dict to keep track of existing materials
37 existing_materials = {"Concrete": Concrete, "Brick": Brick, "Stone": Stone,
38 "Wood": Wood, "Steel": Steel}
39 used_materials = [] # keeping the used positional arguments here
40 available_materials = [] # valid materials for can_build_together()
41
42 def __init__(self):
43 self.materials = [] # valid materials for can_build()
44
45 def __call__(self, *args, **kwargs):
46 if (len(args) and len(kwargs)
47 or not len(args) and not len(kwargs)):
48 raise ValueError("Only positional or only keyword arguments allowed.")
49
50 if len(kwargs):
51 created_materials = []
52 for material in kwargs.items():
53 if material[0] not in self.existing_materials:
54 raise ValueError(f"No such material: {material[0]}.")
55 created_materials.append(self.existing_materials[material[0]](material[1]))
56 self.materials.extend(created_materials)
57 Factory.available_materials.extend(created_materials)
58 return tuple(created_materials)
59
60 # else: len(args) > 0
61 for pos_arg in args:
62 if pos_arg in Factory.used_materials:
63 raise AssertionError("Material has already been used.")
64 if pos_arg in self.materials:
65 self.materials.remove(pos_arg)
66
67 # in case a material from another factory is passed
68 if pos_arg in Factory.available_materials:
69 Factory.available_materials.remove(pos_arg)
70 Factory.used_materials.append(pos_arg)
71
72 materials_to_combine = list(map(lambda x: type(x).__name__, args))
73 for element in materials_to_combine:
74 separated_element = element.split('_')
75 materials_to_combine.remove(element)
76 materials_to_combine.extend(separated_element)
77 materials_to_combine.sort()
78 new_material_name = '_'.join(materials_to_combine)
79
80 sum_weights = sum(list(map(lambda x: x.weight, args)))
81 sum_density = sum(list(map(lambda x: x.density,
82 [cls for cls in self.existing_materials.values()
83 if cls.__name__ in materials_to_combine])))
84
85 if new_material_name not in self.existing_materials:
86 self.existing_materials[new_material_name] = type(
87 new_material_name,
88 (Material,),
89 {"density": sum_density / len(materials_to_combine)}
90 )
91 new_material = self.existing_materials[new_material_name](sum_weights)
92 self.materials.append(new_material)
93 Factory.available_materials.append(new_material)
94 return new_material
95
96 def can_build(self, wall_volume):
97 """Whether instance's materials' volumes are enough to build a wall."""
98 # in a separate list in case another factory has used materials from the current one
99 valid_materials = [material for material in self.materials
100 if material in Factory.available_materials]
101 return sum(list(map(lambda x: x.volume, valid_materials))) >= wall_volume
102
103 @classmethod
104 def can_build_together(cls, wall_volume):
105 """Whether class' materials' volumes are enough to build a wall."""
106 return sum(list(map(lambda x: x.volume, Factory.available_materials))) >= wall_volume
.....FF...
======================================================================
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 63, in __call__
raise AssertionError("Material has already been used.")
AssertionError: 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 128, in test_positional_arguments_multiple_argument_with_dynamics
self.assertEqual(mega_material.__class__.__name__,
AssertionError: 'Brick_Concrete_Stone_Steel_Wood' != 'Brick_Concrete_Steel_Stone_Wood'
- Brick_Concrete_Stone_Steel_Wood
? ------
+ Brick_Concrete_Steel_Stone_Wood
? ++++++
----------------------------------------------------------------------
Ran 10 tests in 0.011s
FAILED (failures=2)
f | 1 | class Material: | f | 1 | class Material: |
2 | """Base material class.""" | 2 | """Base material class.""" | ||
3 | density = 1 | 3 | density = 1 | ||
4 | 4 | ||||
5 | def __init__(self, weight): | 5 | def __init__(self, weight): | ||
6 | self.weight = weight | 6 | self.weight = weight | ||
7 | 7 | ||||
8 | @property | 8 | @property | ||
9 | def volume(self): | 9 | def volume(self): | ||
10 | """Calculate material's volume.""" | 10 | """Calculate material's volume.""" | ||
11 | return self.weight / self.density | 11 | return self.weight / self.density | ||
12 | 12 | ||||
13 | 13 | ||||
14 | class Concrete(Material): | 14 | class Concrete(Material): | ||
15 | density = 2500 | 15 | density = 2500 | ||
16 | 16 | ||||
17 | 17 | ||||
18 | class Brick(Material): | 18 | class Brick(Material): | ||
19 | density = 2000 | 19 | density = 2000 | ||
20 | 20 | ||||
21 | 21 | ||||
22 | class Stone(Material): | 22 | class Stone(Material): | ||
23 | density = 1600 | 23 | density = 1600 | ||
24 | 24 | ||||
25 | 25 | ||||
26 | class Wood(Material): | 26 | class Wood(Material): | ||
27 | density = 600 | 27 | density = 600 | ||
28 | 28 | ||||
29 | 29 | ||||
30 | class Steel(Material): | 30 | class Steel(Material): | ||
31 | density = 7700 | 31 | density = 7700 | ||
32 | 32 | ||||
33 | 33 | ||||
n | 34 | # global dict to keep track of existing materials | n | ||
35 | existing_materials = {"Concrete": Concrete, "Brick": Brick, "Stone": Stone, | ||||
36 | "Wood": Wood, "Steel": Steel} | ||||
37 | |||||
38 | |||||
39 | class Factory: | 34 | class Factory: | ||
40 | """Create (new) materials.""" | 35 | """Create (new) materials.""" | ||
n | n | 36 | # dict to keep track of existing materials | ||
37 | existing_materials = {"Concrete": Concrete, "Brick": Brick, "Stone": Stone, | ||||
38 | "Wood": Wood, "Steel": Steel} | ||||
41 | used_materials = [] # keeping the used positional arguments here | 39 | used_materials = [] # keeping the used positional arguments here | ||
42 | available_materials = [] # valid materials for can_build_together() | 40 | available_materials = [] # valid materials for can_build_together() | ||
43 | 41 | ||||
44 | def __init__(self): | 42 | def __init__(self): | ||
45 | self.materials = [] # valid materials for can_build() | 43 | self.materials = [] # valid materials for can_build() | ||
46 | 44 | ||||
47 | def __call__(self, *args, **kwargs): | 45 | def __call__(self, *args, **kwargs): | ||
48 | if (len(args) and len(kwargs) | 46 | if (len(args) and len(kwargs) | ||
49 | or not len(args) and not len(kwargs)): | 47 | or not len(args) and not len(kwargs)): | ||
50 | raise ValueError("Only positional or only keyword arguments allowed.") | 48 | raise ValueError("Only positional or only keyword arguments allowed.") | ||
51 | 49 | ||||
52 | if len(kwargs): | 50 | if len(kwargs): | ||
53 | created_materials = [] | 51 | created_materials = [] | ||
n | 54 | for material in kwargs: | n | 52 | for material in kwargs.items(): |
55 | if material not in existing_materials: | 53 | if material[0] not in self.existing_materials: | ||
56 | raise ValueError(f"No such material: {material}.") | 54 | raise ValueError(f"No such material: {material[0]}.") | ||
57 | created_materials.append(existing_materials[material](kwargs[material])) | 55 | created_materials.append(self.existing_materials[material[0]](material[1])) | ||
58 | self.materials.extend(created_materials) | 56 | self.materials.extend(created_materials) | ||
59 | Factory.available_materials.extend(created_materials) | 57 | Factory.available_materials.extend(created_materials) | ||
60 | return tuple(created_materials) | 58 | return tuple(created_materials) | ||
61 | 59 | ||||
62 | # else: len(args) > 0 | 60 | # else: len(args) > 0 | ||
n | 63 | for used in args: | n | 61 | for pos_arg in args: |
64 | if used in Factory.used_materials: | 62 | if pos_arg in Factory.used_materials: | ||
65 | raise AssertionError("Material has already been used.") | 63 | raise AssertionError("Material has already been used.") | ||
n | 66 | if used in self.materials: | n | 64 | if pos_arg in self.materials: |
67 | self.materials.remove(used) | 65 | self.materials.remove(pos_arg) | ||
68 | 66 | ||||
69 | # in case a material from another factory is passed | 67 | # in case a material from another factory is passed | ||
n | 70 | if used in Factory.available_materials: | n | 68 | if pos_arg in Factory.available_materials: |
71 | Factory.available_materials.remove(used) | 69 | Factory.available_materials.remove(pos_arg) | ||
72 | Factory.used_materials.append(used) | 70 | Factory.used_materials.append(pos_arg) | ||
73 | 71 | ||||
74 | materials_to_combine = list(map(lambda x: type(x).__name__, args)) | 72 | materials_to_combine = list(map(lambda x: type(x).__name__, args)) | ||
75 | for element in materials_to_combine: | 73 | for element in materials_to_combine: | ||
n | 76 | if '_' in element: | n | ||
77 | separated_element = element.split('_') | 74 | separated_element = element.split('_') | ||
78 | materials_to_combine.remove(element) | 75 | materials_to_combine.remove(element) | ||
79 | materials_to_combine.extend(separated_element) | 76 | materials_to_combine.extend(separated_element) | ||
80 | materials_to_combine.sort() | 77 | materials_to_combine.sort() | ||
81 | new_material_name = '_'.join(materials_to_combine) | 78 | new_material_name = '_'.join(materials_to_combine) | ||
82 | 79 | ||||
83 | sum_weights = sum(list(map(lambda x: x.weight, args))) | 80 | sum_weights = sum(list(map(lambda x: x.weight, args))) | ||
n | 84 | sum_density = sum(list(map(lambda x: x.density, [cls for cls in existing_materials.values() | n | 81 | sum_density = sum(list(map(lambda x: x.density, |
82 | [cls for cls in self.existing_materials.values() | ||||
85 | if cls.__name__ in materials_to_combine]))) | 83 | if cls.__name__ in materials_to_combine]))) | ||
86 | 84 | ||||
n | 87 | if new_material_name not in existing_materials: | n | 85 | if new_material_name not in self.existing_materials: |
88 | existing_materials[new_material_name] = type( | 86 | self.existing_materials[new_material_name] = type( | ||
89 | new_material_name, | 87 | new_material_name, | ||
90 | (Material,), | 88 | (Material,), | ||
91 | {"density": sum_density / len(materials_to_combine)} | 89 | {"density": sum_density / len(materials_to_combine)} | ||
92 | ) | 90 | ) | ||
t | 93 | to_add = existing_materials[new_material_name](sum_weights) | t | 91 | new_material = self.existing_materials[new_material_name](sum_weights) |
94 | self.materials.append(to_add) | 92 | self.materials.append(new_material) | ||
95 | Factory.available_materials.append(to_add) | 93 | Factory.available_materials.append(new_material) | ||
96 | return to_add | 94 | return new_material | ||
97 | 95 | ||||
98 | def can_build(self, wall_volume): | 96 | def can_build(self, wall_volume): | ||
99 | """Whether instance's materials' volumes are enough to build a wall.""" | 97 | """Whether instance's materials' volumes are enough to build a wall.""" | ||
100 | # in a separate list in case another factory has used materials from the current one | 98 | # in a separate list in case another factory has used materials from the current one | ||
101 | valid_materials = [material for material in self.materials | 99 | valid_materials = [material for material in self.materials | ||
102 | if material in Factory.available_materials] | 100 | if material in Factory.available_materials] | ||
103 | return sum(list(map(lambda x: x.volume, valid_materials))) >= wall_volume | 101 | return sum(list(map(lambda x: x.volume, valid_materials))) >= wall_volume | ||
104 | 102 | ||||
105 | @classmethod | 103 | @classmethod | ||
106 | def can_build_together(cls, wall_volume): | 104 | def can_build_together(cls, wall_volume): | ||
107 | """Whether class' materials' volumes are enough to build a wall.""" | 105 | """Whether class' materials' volumes are enough to build a wall.""" | ||
108 | return sum(list(map(lambda x: x.volume, Factory.available_materials))) >= wall_volume | 106 | return sum(list(map(lambda x: x.volume, Factory.available_materials))) >= wall_volume |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|