1from itertools import chain, starmap
2from statistics import mean
3
4
5class AddByVolumeMixin:
6 """Allow adding isntances by their volume."""
7
8 __radd__ = lambda self, other: self.volume + getattr(other, 'volume', other)
9
10
11class Material(AddByVolumeMixin):
12 """Represent a material."""
13
14 def __init__(self, mass):
15 self.mass = mass
16 self.obsolete = False
17
18 def use(self):
19 """Ensure not used so far and make obsolete."""
20 assert not self.obsolete and not setattr(self, 'obsolete', True)
21
22 @property
23 def volume(self):
24 """Get volume of an instance."""
25 return self.mass / self.density
26
27 @property
28 def primitive_cls_names(self):
29 """Get list of primitive class names used by this instance's class."""
30 return self.__class__.__name__.split('_')
31
32
33class Factory(AddByVolumeMixin):
34 """Factory that generates materials."""
35
36 _factories = [] # All Factory instances
37 _classes = {} # All known material types behind their name as str
38
39 def __init__(self):
40 self._factories.append(self)
41 self._products = []
42
43 @staticmethod
44 def _store_output(fun):
45 """Store objects getting out for future reference."""
46 def decorated(self, *args, **kwargs):
47 result = fun(self, *args, **kwargs)
48 self._products.extend(result if type(result) is tuple else [result])
49 return result
50 return decorated
51
52 @_store_output
53 def __call__(self, *args, **kwargs):
54 """Create new materials."""
55 if not bool(args) ^ bool(kwargs):
56 raise ValueError
57 if kwargs:
58 return tuple(self._from_classnames(**kwargs))
59 [obj.use() for obj in args] # Make materials obsolete
60
61 return self._from_products(*args)
62
63 @staticmethod
64 def _objs_prims(objects):
65 """Get a sorted list of all promitives' names from a list of objects."""
66 return sorted(chain(*map(lambda obj: obj.primitive_cls_names, objects)))
67
68 def _from_classnames(self, **kwargs):
69 """Create materials from classnames (instance called with kwargs)."""
70 if not set(kwargs.keys()).issubset(set(self._classes)):
71 raise ValueError
72 yield from (self._classes[name](mass) for name, mass in kwargs.items())
73
74 def _from_products(self, *args):
75 """Create materials from instances (instance called with args)."""
76 cls = self.generate_material_class('_'.join(self._objs_prims(args)))
77 return cls(sum(map(lambda obj: obj.mass, args)))
78
79 @classmethod
80 def generate_material_class(cls, name, density=None):
81 """Generate a new class if not existing, else return existing."""
82 if name not in cls._classes:
83 prims = map(cls._classes.__getitem__, name.split('_'))
84 density = density or mean(map(lambda obj: obj.density, prims))
85 cls._classes[name] = type(name, (Material,), {'density': density})
86 return cls._classes[name]
87
88 @property
89 def volume(self):
90 """Volume generated by this factory."""
91 return sum(filter(lambda prd: not prd.obsolete, self._products))
92
93 @classmethod
94 def can_build_together(cls, volume):
95 """Whether can build a wall from all factories' volume."""
96 return sum(cls._factories) >= volume
97
98 def can_build(self, volume):
99 """Whether can build a wall from this factory's volume."""
100 return self.volume >= volume
101
102
103_PRIMITIVES = {'Concrete': 2500, 'Brick': 2000,
104 'Stone': 1600, 'Wood': 600, 'Steel': 7700}
105Concrete, Brick, Stone, Wood, Steel = starmap(Factory.generate_material_class,
106 _PRIMITIVES.items())
.....F....
======================================================================
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 47, in decorated
result = fun(self, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 59, in __call__
[obj.use() for obj in args] # Make materials obsolete
^^^^^^^^^
File "/tmp/solution.py", line 20, in use
assert not self.obsolete and not setattr(self, 'obsolete', True)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError
----------------------------------------------------------------------
Ran 10 tests in 0.012s
FAILED (failures=1)
n | 1 | class Material: | n | 1 | from itertools import chain, starmap |
2 | from statistics import mean | ||||
3 | |||||
4 | |||||
5 | class AddByVolumeMixin: | ||||
6 | """Allow adding isntances by their volume.""" | ||||
7 | |||||
8 | __radd__ = lambda self, other: self.volume + getattr(other, 'volume', other) | ||||
9 | |||||
10 | |||||
11 | class Material(AddByVolumeMixin): | ||||
12 | """Represent a material.""" | ||||
13 | |||||
2 | def __init__(self, mass): | 14 | def __init__(self, mass): | ||
n | 3 | self._mass = mass | n | 15 | self.mass = mass |
4 | self._used = False | 16 | self.obsolete = False | ||
5 | 17 | ||||
n | 6 | @property | n | ||
7 | def mass(self): | 18 | def use(self): | ||
8 | return self._mass | 19 | """Ensure not used so far and make obsolete.""" | ||
20 | assert not self.obsolete and not setattr(self, 'obsolete', True) | ||||
9 | 21 | ||||
10 | @property | 22 | @property | ||
11 | def volume(self): | 23 | def volume(self): | ||
n | n | 24 | """Get volume of an instance.""" | ||
12 | return float(self._mass) / self.density | 25 | return self.mass / self.density | ||
13 | 26 | ||||
n | 14 | class Concrete(Material): | n | 27 | @property |
15 | density = 2500 | 28 | def primitive_cls_names(self): | ||
29 | """Get list of primitive class names used by this instance's class.""" | ||||
30 | return self.__class__.__name__.split('_') | ||||
16 | 31 | ||||
n | 17 | class Brick(Material): | n | ||
18 | density = 2000 | ||||
19 | 32 | ||||
n | 20 | class Stone(Material): | n | 33 | class Factory(AddByVolumeMixin): |
21 | density = 1600 | 34 | """Factory that generates materials.""" | ||
22 | 35 | ||||
n | 23 | class Wood(Material): | n | 36 | _factories = [] # All Factory instances |
24 | density = 600 | 37 | _classes = {} # All known material types behind their name as str | ||
25 | |||||
26 | class Steel(Material): | ||||
27 | density = 7700 | ||||
28 | |||||
29 | class Factory: | ||||
30 | _created_materials = [] | ||||
31 | _alloy_classes = {} | ||||
32 | 38 | ||||
33 | def __init__(self): | 39 | def __init__(self): | ||
n | n | 40 | self._factories.append(self) | ||
34 | self._materials = [] | 41 | self._products = [] | ||
35 | 42 | ||||
n | n | 43 | @staticmethod | ||
44 | def _store_output(fun): | ||||
45 | """Store objects getting out for future reference.""" | ||||
46 | def decorated(self, *args, **kwargs): | ||||
47 | result = fun(self, *args, **kwargs) | ||||
48 | self._products.extend(result if type(result) is tuple else [result]) | ||||
49 | return result | ||||
50 | return decorated | ||||
51 | |||||
52 | @_store_output | ||||
36 | def __call__(self, *args, **kwargs): | 53 | def __call__(self, *args, **kwargs): | ||
n | 37 | if not args and not kwargs: | n | 54 | """Create new materials.""" |
38 | raise ValueError("Arguments required") | 55 | if not bool(args) ^ bool(kwargs): | ||
56 | raise ValueError | ||||
57 | if kwargs: | ||||
58 | return tuple(self._from_classnames(**kwargs)) | ||||
59 | [obj.use() for obj in args] # Make materials obsolete | ||||
39 | 60 | ||||
n | 40 | if args and kwargs: | n | 61 | return self._from_products(*args) |
41 | raise ValueError("Cannot mix positional and keyword arguments") | ||||
42 | 62 | ||||
n | 43 | if kwargs: | n | 63 | @staticmethod |
44 | return self._handle_kwargs(kwargs) | 64 | def _objs_prims(objects): | ||
65 | """Get a sorted list of all promitives' names from a list of objects.""" | ||||
66 | return sorted(chain(*map(lambda obj: obj.primitive_cls_names, objects))) | ||||
45 | 67 | ||||
n | 46 | return self._handle_args(args) | n | 68 | def _from_classnames(self, **kwargs): |
69 | """Create materials from classnames (instance called with kwargs).""" | ||||
70 | if not set(kwargs.keys()).issubset(set(self._classes)): | ||||
71 | raise ValueError | ||||
72 | yield from (self._classes[name](mass) for name, mass in kwargs.items()) | ||||
47 | 73 | ||||
n | 48 | def _handle_kwargs(self, kwargs): | n | 74 | def _from_products(self, *args): |
49 | result = [] | 75 | """Create materials from instances (instance called with args).""" | ||
50 | for name, mass in kwargs.items(): | 76 | cls = self.generate_material_class('_'.join(self._objs_prims(args))) | ||
51 | cls = self._get_material_class(name) | 77 | return cls(sum(map(lambda obj: obj.mass, args))) | ||
52 | material = cls(mass) | ||||
53 | self._materials.append(material) | ||||
54 | result.append(material) | ||||
55 | return tuple(result) | ||||
56 | 78 | ||||
n | 57 | def _handle_args(self, args): | n | 79 | @classmethod |
58 | for arg in args: | 80 | def generate_material_class(cls, name, density=None): | ||
59 | if getattr(arg, '_used', False): | 81 | """Generate a new class if not existing, else return existing.""" | ||
60 | raise AssertionError("Material already used") | 82 | if name not in cls._classes: | ||
61 | arg._used = True | 83 | prims = map(cls._classes.__getitem__, name.split('_')) | ||
84 | density = density or mean(map(lambda obj: obj.density, prims)) | ||||
85 | cls._classes[name] = type(name, (Material,), {'density': density}) | ||||
86 | return cls._classes[name] | ||||
62 | 87 | ||||
n | 63 | base_classes = self._get_base_classes(args) | n | 88 | @property |
64 | class_name = '_'.join(sorted(cls.__name__ for cls in base_classes)) | 89 | def volume(self): | ||
65 | 90 | """Volume generated by this factory.""" | |||
66 | if class_name not in Factory._alloy_classes: | 91 | return sum(filter(lambda prd: not prd.obsolete, self._products)) | ||
67 | density = sum(cls.density for cls in base_classes) / len(base_classes) | ||||
68 | Factory._alloy_classes[class_name] = type(class_name, (Material,), {'density': density}) | ||||
69 | |||||
70 | total_mass = sum(arg.mass for arg in args) | ||||
71 | result = Factory._alloy_classes[class_name](total_mass) | ||||
72 | self._materials.append(result) | ||||
73 | return result | ||||
74 | |||||
75 | def _get_material_class(self, name): | ||||
76 | for cls in [Concrete, Brick, Stone, Wood, Steel] + list(Factory._alloy_classes.values()): | ||||
77 | if cls.__name__ == name: | ||||
78 | return cls | ||||
79 | raise ValueError(f"Invalid material name: {name}") | ||||
80 | |||||
81 | def _get_base_classes(self, materials): | ||||
82 | result = set() | ||||
83 | for material in materials: | ||||
84 | material_type = type(material) | ||||
85 | if material_type in [Concrete, Brick, Stone, Wood, Steel]: | ||||
86 | result.add(material_type) | ||||
87 | else: | ||||
88 | class_name = material_type.__name__ | ||||
89 | base_names = class_name.split('_') | ||||
90 | for name in base_names: | ||||
91 | for cls in [Concrete, Brick, Stone, Wood, Steel]: | ||||
92 | if cls.__name__ == name: | ||||
93 | result.add(cls) | ||||
94 | return result | ||||
95 | |||||
96 | def can_build(self, volume): | ||||
97 | total_volume = sum(m.volume for m in self._materials if not getattr(m, '_used', False)) | ||||
98 | return total_volume >= volume | ||||
99 | 92 | ||||
100 | @classmethod | 93 | @classmethod | ||
101 | def can_build_together(cls, volume): | 94 | def can_build_together(cls, volume): | ||
t | 102 | total_volume = sum(m.volume for m in cls._created_materials if not getattr(m, '_used', False)) | t | 95 | """Whether can build a wall from all factories' volume.""" |
96 | return sum(cls._factories) >= volume | ||||
97 | |||||
98 | def can_build(self, volume): | ||||
99 | """Whether can build a wall from this factory's volume.""" | ||||
103 | return total_volume >= volume | 100 | return self.volume >= volume | ||
101 | |||||
102 | |||||
103 | _PRIMITIVES = {'Concrete': 2500, 'Brick': 2000, | ||||
104 | 'Stone': 1600, 'Wood': 600, 'Steel': 7700} | ||||
105 | Concrete, Brick, Stone, Wood, Steel = starmap(Factory.generate_material_class, | ||||
106 | _PRIMITIVES.items()) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
n | 1 | from itertools import chain, starmap | n | 1 | class Material: |
2 | from statistics import mean | 2 | def __init__(self, mass): | ||
3 | self._mass = mass | ||||
4 | self._used = False | ||||
3 | 5 | ||||
n | 4 | n | 6 | @property | |
5 | class AddByVolumeMixin: | ||||
6 | """Allow adding isntances by their volume.""" | ||||
7 | |||||
8 | __radd__ = lambda self, other: self.volume + getattr(other, 'volume', other) | ||||
9 | |||||
10 | |||||
11 | class Material(AddByVolumeMixin): | ||||
12 | """Represent a material.""" | ||||
13 | |||||
14 | def __init__(self, mass): | ||||
15 | self.mass = mass | ||||
16 | self.obsolete = False | ||||
17 | |||||
18 | def use(self): | 7 | def mass(self): | ||
19 | """Ensure not used so far and make obsolete.""" | 8 | return self._mass | ||
20 | assert not self.obsolete and not setattr(self, 'obsolete', True) | ||||
21 | 9 | ||||
22 | @property | 10 | @property | ||
23 | def volume(self): | 11 | def volume(self): | ||
n | 24 | """Get volume of an instance.""" | n | ||
25 | return self.mass / self.density | 12 | return float(self._mass) / self.density | ||
26 | 13 | ||||
n | 27 | @property | n | 14 | class Concrete(Material): |
28 | def primitive_cls_names(self): | 15 | density = 2500 | ||
29 | """Get list of primitive class names used by this instance's class.""" | ||||
30 | return self.__class__.__name__.split('_') | ||||
31 | 16 | ||||
n | n | 17 | class Brick(Material): | ||
18 | density = 2000 | ||||
32 | 19 | ||||
n | 33 | class Factory(AddByVolumeMixin): | n | 20 | class Stone(Material): |
34 | """Factory that generates materials.""" | 21 | density = 1600 | ||
35 | 22 | ||||
n | 36 | _factories = [] # All Factory instances | n | 23 | class Wood(Material): |
37 | _classes = {} # All known material types behind their name as str | 24 | density = 600 | ||
25 | |||||
26 | class Steel(Material): | ||||
27 | density = 7700 | ||||
28 | |||||
29 | class Factory: | ||||
30 | _created_materials = [] | ||||
31 | _alloy_classes = {} | ||||
38 | 32 | ||||
39 | def __init__(self): | 33 | def __init__(self): | ||
n | 40 | self._factories.append(self) | n | ||
41 | self._products = [] | 34 | self._materials = [] | ||
42 | 35 | ||||
n | 43 | @staticmethod | n | ||
44 | def _store_output(fun): | ||||
45 | """Store objects getting out for future reference.""" | ||||
46 | def decorated(self, *args, **kwargs): | 36 | def __call__(self, *args, **kwargs): | ||
47 | result = fun(self, *args, **kwargs) | 37 | if not args and not kwargs: | ||
48 | self._products.extend(result if type(result) is tuple else [result]) | 38 | raise ValueError("Arguments required") | ||
49 | return result | ||||
50 | return decorated | ||||
51 | 39 | ||||
n | 52 | @_store_output | n | 40 | if args and kwargs: |
53 | def __call__(self, *args, **kwargs): | 41 | raise ValueError("Cannot mix positional and keyword arguments") | ||
54 | """Create new materials.""" | 42 | |||
55 | if not bool(args) ^ bool(kwargs): | ||||
56 | raise ValueError | ||||
57 | if kwargs: | 43 | if kwargs: | ||
n | 58 | return tuple(self._from_classnames(**kwargs)) | n | 44 | return self._handle_kwargs(kwargs) |
59 | [obj.use() for obj in args] # Make materials obsolete | ||||
60 | return self._from_products(*args) | ||||
61 | 45 | ||||
n | 62 | @staticmethod | n | 46 | return self._handle_args(args) |
63 | def _objs_prims(objects): | ||||
64 | """Get a sorted list of all promitives' names from a list of objects.""" | ||||
65 | return sorted(chain(*map(lambda obj: obj.primitive_cls_names, objects))) | ||||
66 | 47 | ||||
n | 67 | def _from_classnames(self, **kwargs): | n | 48 | def _handle_kwargs(self, kwargs): |
68 | """Create materials from classnames (instance called with kwargs).""" | 49 | result = [] | ||
69 | if not set(kwargs.keys()).issubset(set(self._classes)): | 50 | for name, mass in kwargs.items(): | ||
70 | raise ValueError | 51 | cls = self._get_material_class(name) | ||
71 | yield from (self._classes[name](mass) for name, mass in kwargs.items()) | 52 | material = cls(mass) | ||
53 | self._materials.append(material) | ||||
54 | result.append(material) | ||||
55 | return tuple(result) | ||||
72 | 56 | ||||
n | 73 | def _from_products(self, *args): | n | 57 | def _handle_args(self, args): |
74 | """Create materials from instances (instance called with args).""" | 58 | for arg in args: | ||
75 | cls = self.generate_material_class('_'.join(self._objs_prims(args))) | 59 | if getattr(arg, '_used', False): | ||
76 | return cls(sum(map(lambda obj: obj.mass, args))) | 60 | raise AssertionError("Material already used") | ||
61 | arg._used = True | ||||
77 | 62 | ||||
n | 78 | @classmethod | n | 63 | base_classes = self._get_base_classes(args) |
79 | def generate_material_class(cls, name, density=None): | 64 | class_name = '_'.join(sorted(cls.__name__ for cls in base_classes)) | ||
80 | """Generate a new class if not existing, else return existing.""" | ||||
81 | if name not in cls._classes: | ||||
82 | prims = map(cls._classes.__getitem__, name.split('_')) | ||||
83 | density = density or mean(map(lambda obj: obj.density, prims)) | ||||
84 | cls._classes[name] = type(name, (Material,), {'density': density}) | ||||
85 | return cls._classes[name] | ||||
86 | 65 | ||||
n | 87 | @property | n | 66 | if class_name not in Factory._alloy_classes: |
88 | def volume(self): | 67 | density = sum(cls.density for cls in base_classes) / len(base_classes) | ||
89 | """Volume generated by this factory.""" | 68 | Factory._alloy_classes[class_name] = type(class_name, (Material,), {'density': density}) | ||
90 | return sum(filter(lambda prd: not prd.obsolete, self._products)) | 69 | |||
70 | total_mass = sum(arg.mass for arg in args) | ||||
71 | result = Factory._alloy_classes[class_name](total_mass) | ||||
72 | self._materials.append(result) | ||||
73 | return result | ||||
74 | |||||
75 | def _get_material_class(self, name): | ||||
76 | for cls in [Concrete, Brick, Stone, Wood, Steel] + list(Factory._alloy_classes.values()): | ||||
77 | if cls.__name__ == name: | ||||
78 | return cls | ||||
79 | raise ValueError(f"Invalid material name: {name}") | ||||
80 | |||||
81 | def _get_base_classes(self, materials): | ||||
82 | result = set() | ||||
83 | for material in materials: | ||||
84 | material_type = type(material) | ||||
85 | if material_type in [Concrete, Brick, Stone, Wood, Steel]: | ||||
86 | result.add(material_type) | ||||
87 | else: | ||||
88 | class_name = material_type.__name__ | ||||
89 | base_names = class_name.split('_') | ||||
90 | for name in base_names: | ||||
91 | for cls in [Concrete, Brick, Stone, Wood, Steel]: | ||||
92 | if cls.__name__ == name: | ||||
93 | result.add(cls) | ||||
94 | return result | ||||
95 | |||||
96 | def can_build(self, volume): | ||||
97 | total_volume = sum(m.volume for m in self._materials if not getattr(m, '_used', False)) | ||||
98 | return total_volume >= volume | ||||
91 | 99 | ||||
92 | @classmethod | 100 | @classmethod | ||
93 | def can_build_together(cls, volume): | 101 | def can_build_together(cls, volume): | ||
t | 94 | """Whether can build a wall from all factories' volume.""" | t | 102 | total_volume = sum(m.volume for m in cls._created_materials if not getattr(m, '_used', False)) |
95 | return sum(cls._factories) >= volume | ||||
96 | |||||
97 | def can_build(self, volume): | ||||
98 | """Whether can build a wall from this factory's volume.""" | ||||
99 | return self.volume >= volume | 103 | return total_volume >= volume | ||
100 | |||||
101 | |||||
102 | _PRIMITIVES = {'Concrete': 2500, 'Brick': 2000, | ||||
103 | 'Stone': 1600, 'Wood': 600, 'Steel': 7700} | ||||
104 | Concrete, Brick, Stone, Wood, Steel = starmap(Factory.generate_material_class, | ||||
105 | _PRIMITIVES.items()) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | from itertools import chain, starmap | f | 1 | from itertools import chain, starmap |
2 | from statistics import mean | 2 | from statistics import mean | ||
3 | 3 | ||||
4 | 4 | ||||
5 | class AddByVolumeMixin: | 5 | class AddByVolumeMixin: | ||
6 | """Allow adding isntances by their volume.""" | 6 | """Allow adding isntances by their volume.""" | ||
7 | 7 | ||||
8 | __radd__ = lambda self, other: self.volume + getattr(other, 'volume', other) | 8 | __radd__ = lambda self, other: self.volume + getattr(other, 'volume', other) | ||
9 | 9 | ||||
10 | 10 | ||||
11 | class Material(AddByVolumeMixin): | 11 | class Material(AddByVolumeMixin): | ||
12 | """Represent a material.""" | 12 | """Represent a material.""" | ||
13 | 13 | ||||
14 | def __init__(self, mass): | 14 | def __init__(self, mass): | ||
15 | self.mass = mass | 15 | self.mass = mass | ||
16 | self.obsolete = False | 16 | self.obsolete = False | ||
17 | 17 | ||||
18 | def use(self): | 18 | def use(self): | ||
19 | """Ensure not used so far and make obsolete.""" | 19 | """Ensure not used so far and make obsolete.""" | ||
20 | assert not self.obsolete and not setattr(self, 'obsolete', True) | 20 | assert not self.obsolete and not setattr(self, 'obsolete', True) | ||
21 | 21 | ||||
22 | @property | 22 | @property | ||
23 | def volume(self): | 23 | def volume(self): | ||
24 | """Get volume of an instance.""" | 24 | """Get volume of an instance.""" | ||
25 | return self.mass / self.density | 25 | return self.mass / self.density | ||
26 | 26 | ||||
27 | @property | 27 | @property | ||
28 | def primitive_cls_names(self): | 28 | def primitive_cls_names(self): | ||
29 | """Get list of primitive class names used by this instance's class.""" | 29 | """Get list of primitive class names used by this instance's class.""" | ||
30 | return self.__class__.__name__.split('_') | 30 | return self.__class__.__name__.split('_') | ||
31 | 31 | ||||
32 | 32 | ||||
33 | class Factory(AddByVolumeMixin): | 33 | class Factory(AddByVolumeMixin): | ||
34 | """Factory that generates materials.""" | 34 | """Factory that generates materials.""" | ||
35 | 35 | ||||
36 | _factories = [] # All Factory instances | 36 | _factories = [] # All Factory instances | ||
37 | _classes = {} # All known material types behind their name as str | 37 | _classes = {} # All known material types behind their name as str | ||
38 | 38 | ||||
39 | def __init__(self): | 39 | def __init__(self): | ||
40 | self._factories.append(self) | 40 | self._factories.append(self) | ||
41 | self._products = [] | 41 | self._products = [] | ||
42 | 42 | ||||
43 | @staticmethod | 43 | @staticmethod | ||
44 | def _store_output(fun): | 44 | def _store_output(fun): | ||
45 | """Store objects getting out for future reference.""" | 45 | """Store objects getting out for future reference.""" | ||
46 | def decorated(self, *args, **kwargs): | 46 | def decorated(self, *args, **kwargs): | ||
47 | result = fun(self, *args, **kwargs) | 47 | result = fun(self, *args, **kwargs) | ||
48 | self._products.extend(result if type(result) is tuple else [result]) | 48 | self._products.extend(result if type(result) is tuple else [result]) | ||
49 | return result | 49 | return result | ||
50 | return decorated | 50 | return decorated | ||
51 | 51 | ||||
52 | @_store_output | 52 | @_store_output | ||
53 | def __call__(self, *args, **kwargs): | 53 | def __call__(self, *args, **kwargs): | ||
54 | """Create new materials.""" | 54 | """Create new materials.""" | ||
55 | if not bool(args) ^ bool(kwargs): | 55 | if not bool(args) ^ bool(kwargs): | ||
56 | raise ValueError | 56 | raise ValueError | ||
57 | if kwargs: | 57 | if kwargs: | ||
58 | return tuple(self._from_classnames(**kwargs)) | 58 | return tuple(self._from_classnames(**kwargs)) | ||
59 | [obj.use() for obj in args] # Make materials obsolete | 59 | [obj.use() for obj in args] # Make materials obsolete | ||
60 | return self._from_products(*args) | 60 | return self._from_products(*args) | ||
61 | 61 | ||||
62 | @staticmethod | 62 | @staticmethod | ||
63 | def _objs_prims(objects): | 63 | def _objs_prims(objects): | ||
64 | """Get a sorted list of all promitives' names from a list of objects.""" | 64 | """Get a sorted list of all promitives' names from a list of objects.""" | ||
65 | return sorted(chain(*map(lambda obj: obj.primitive_cls_names, objects))) | 65 | return sorted(chain(*map(lambda obj: obj.primitive_cls_names, objects))) | ||
66 | 66 | ||||
67 | def _from_classnames(self, **kwargs): | 67 | def _from_classnames(self, **kwargs): | ||
68 | """Create materials from classnames (instance called with kwargs).""" | 68 | """Create materials from classnames (instance called with kwargs).""" | ||
69 | if not set(kwargs.keys()).issubset(set(self._classes)): | 69 | if not set(kwargs.keys()).issubset(set(self._classes)): | ||
70 | raise ValueError | 70 | raise ValueError | ||
71 | yield from (self._classes[name](mass) for name, mass in kwargs.items()) | 71 | yield from (self._classes[name](mass) for name, mass in kwargs.items()) | ||
72 | 72 | ||||
73 | def _from_products(self, *args): | 73 | def _from_products(self, *args): | ||
74 | """Create materials from instances (instance called with args).""" | 74 | """Create materials from instances (instance called with args).""" | ||
75 | cls = self.generate_material_class('_'.join(self._objs_prims(args))) | 75 | cls = self.generate_material_class('_'.join(self._objs_prims(args))) | ||
76 | return cls(sum(map(lambda obj: obj.mass, args))) | 76 | return cls(sum(map(lambda obj: obj.mass, args))) | ||
77 | 77 | ||||
78 | @classmethod | 78 | @classmethod | ||
79 | def generate_material_class(cls, name, density=None): | 79 | def generate_material_class(cls, name, density=None): | ||
80 | """Generate a new class if not existing, else return existing.""" | 80 | """Generate a new class if not existing, else return existing.""" | ||
81 | if name not in cls._classes: | 81 | if name not in cls._classes: | ||
82 | prims = map(cls._classes.__getitem__, name.split('_')) | 82 | prims = map(cls._classes.__getitem__, name.split('_')) | ||
83 | density = density or mean(map(lambda obj: obj.density, prims)) | 83 | density = density or mean(map(lambda obj: obj.density, prims)) | ||
84 | cls._classes[name] = type(name, (Material,), {'density': density}) | 84 | cls._classes[name] = type(name, (Material,), {'density': density}) | ||
85 | return cls._classes[name] | 85 | return cls._classes[name] | ||
86 | 86 | ||||
87 | @property | 87 | @property | ||
88 | def volume(self): | 88 | def volume(self): | ||
89 | """Volume generated by this factory.""" | 89 | """Volume generated by this factory.""" | ||
90 | return sum(filter(lambda prd: not prd.obsolete, self._products)) | 90 | return sum(filter(lambda prd: not prd.obsolete, self._products)) | ||
91 | 91 | ||||
92 | @classmethod | 92 | @classmethod | ||
93 | def can_build_together(cls, volume): | 93 | def can_build_together(cls, volume): | ||
94 | """Whether can build a wall from all factories' volume.""" | 94 | """Whether can build a wall from all factories' volume.""" | ||
95 | return sum(cls._factories) >= volume | 95 | return sum(cls._factories) >= volume | ||
96 | 96 | ||||
97 | def can_build(self, volume): | 97 | def can_build(self, volume): | ||
98 | """Whether can build a wall from this factory's volume.""" | 98 | """Whether can build a wall from this factory's volume.""" | ||
99 | return self.volume >= volume | 99 | return self.volume >= volume | ||
100 | 100 | ||||
t | t | 101 | |||
101 | _PRIMITIVES = {'Concrete': 2500, 'Brick': 2000, | 102 | _PRIMITIVES = {'Concrete': 2500, 'Brick': 2000, | ||
102 | 'Stone': 1600, 'Wood': 600, 'Steel': 7700} | 103 | 'Stone': 1600, 'Wood': 600, 'Steel': 7700} | ||
103 | Concrete, Brick, Stone, Wood, Steel = starmap(Factory.generate_material_class, | 104 | Concrete, Brick, Stone, Wood, Steel = starmap(Factory.generate_material_class, | ||
104 | _PRIMITIVES.items()) | 105 | _PRIMITIVES.items()) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | from itertools import chain, starmap | f | 1 | from itertools import chain, starmap |
2 | from statistics import mean | 2 | from statistics import mean | ||
3 | 3 | ||||
4 | 4 | ||||
5 | class AddByVolumeMixin: | 5 | class AddByVolumeMixin: | ||
6 | """Allow adding isntances by their volume.""" | 6 | """Allow adding isntances by their volume.""" | ||
7 | 7 | ||||
8 | __radd__ = lambda self, other: self.volume + getattr(other, 'volume', other) | 8 | __radd__ = lambda self, other: self.volume + getattr(other, 'volume', other) | ||
9 | 9 | ||||
10 | 10 | ||||
11 | class Material(AddByVolumeMixin): | 11 | class Material(AddByVolumeMixin): | ||
12 | """Represent a material.""" | 12 | """Represent a material.""" | ||
13 | 13 | ||||
14 | def __init__(self, mass): | 14 | def __init__(self, mass): | ||
15 | self.mass = mass | 15 | self.mass = mass | ||
16 | self.obsolete = False | 16 | self.obsolete = False | ||
t | 17 | t | |||
18 | __radd__ = lambda self, other: self.volume + getattr(other, 'volume', other) | ||||
19 | 17 | ||||
20 | def use(self): | 18 | def use(self): | ||
21 | """Ensure not used so far and make obsolete.""" | 19 | """Ensure not used so far and make obsolete.""" | ||
22 | assert not self.obsolete and not setattr(self, 'obsolete', True) | 20 | assert not self.obsolete and not setattr(self, 'obsolete', True) | ||
23 | 21 | ||||
24 | @property | 22 | @property | ||
25 | def volume(self): | 23 | def volume(self): | ||
26 | """Get volume of an instance.""" | 24 | """Get volume of an instance.""" | ||
27 | return self.mass / self.density | 25 | return self.mass / self.density | ||
28 | 26 | ||||
29 | @property | 27 | @property | ||
30 | def primitive_cls_names(self): | 28 | def primitive_cls_names(self): | ||
31 | """Get list of primitive class names used by this instance's class.""" | 29 | """Get list of primitive class names used by this instance's class.""" | ||
32 | return self.__class__.__name__.split('_') | 30 | return self.__class__.__name__.split('_') | ||
33 | 31 | ||||
34 | 32 | ||||
35 | class Factory(AddByVolumeMixin): | 33 | class Factory(AddByVolumeMixin): | ||
36 | """Factory that generates materials.""" | 34 | """Factory that generates materials.""" | ||
37 | 35 | ||||
38 | _factories = [] # All Factory instances | 36 | _factories = [] # All Factory instances | ||
39 | _classes = {} # All known material types behind their name as str | 37 | _classes = {} # All known material types behind their name as str | ||
40 | 38 | ||||
41 | def __init__(self): | 39 | def __init__(self): | ||
42 | self._factories.append(self) | 40 | self._factories.append(self) | ||
43 | self._products = [] | 41 | self._products = [] | ||
44 | 42 | ||||
45 | @staticmethod | 43 | @staticmethod | ||
46 | def _store_output(fun): | 44 | def _store_output(fun): | ||
47 | """Store objects getting out for future reference.""" | 45 | """Store objects getting out for future reference.""" | ||
48 | def decorated(self, *args, **kwargs): | 46 | def decorated(self, *args, **kwargs): | ||
49 | result = fun(self, *args, **kwargs) | 47 | result = fun(self, *args, **kwargs) | ||
50 | self._products.extend(result if type(result) is tuple else [result]) | 48 | self._products.extend(result if type(result) is tuple else [result]) | ||
51 | return result | 49 | return result | ||
52 | return decorated | 50 | return decorated | ||
53 | 51 | ||||
54 | @_store_output | 52 | @_store_output | ||
55 | def __call__(self, *args, **kwargs): | 53 | def __call__(self, *args, **kwargs): | ||
56 | """Create new materials.""" | 54 | """Create new materials.""" | ||
57 | if not bool(args) ^ bool(kwargs): | 55 | if not bool(args) ^ bool(kwargs): | ||
58 | raise ValueError | 56 | raise ValueError | ||
59 | if kwargs: | 57 | if kwargs: | ||
60 | return tuple(self._from_classnames(**kwargs)) | 58 | return tuple(self._from_classnames(**kwargs)) | ||
61 | [obj.use() for obj in args] # Make materials obsolete | 59 | [obj.use() for obj in args] # Make materials obsolete | ||
62 | return self._from_products(*args) | 60 | return self._from_products(*args) | ||
63 | 61 | ||||
64 | @staticmethod | 62 | @staticmethod | ||
65 | def _objs_prims(objects): | 63 | def _objs_prims(objects): | ||
66 | """Get a sorted list of all promitives' names from a list of objects.""" | 64 | """Get a sorted list of all promitives' names from a list of objects.""" | ||
67 | return sorted(chain(*map(lambda obj: obj.primitive_cls_names, objects))) | 65 | return sorted(chain(*map(lambda obj: obj.primitive_cls_names, objects))) | ||
68 | 66 | ||||
69 | def _from_classnames(self, **kwargs): | 67 | def _from_classnames(self, **kwargs): | ||
70 | """Create materials from classnames (instance called with kwargs).""" | 68 | """Create materials from classnames (instance called with kwargs).""" | ||
71 | if not set(kwargs.keys()).issubset(set(self._classes)): | 69 | if not set(kwargs.keys()).issubset(set(self._classes)): | ||
72 | raise ValueError | 70 | raise ValueError | ||
73 | yield from (self._classes[name](mass) for name, mass in kwargs.items()) | 71 | yield from (self._classes[name](mass) for name, mass in kwargs.items()) | ||
74 | 72 | ||||
75 | def _from_products(self, *args): | 73 | def _from_products(self, *args): | ||
76 | """Create materials from instances (instance called with args).""" | 74 | """Create materials from instances (instance called with args).""" | ||
77 | cls = self.generate_material_class('_'.join(self._objs_prims(args))) | 75 | cls = self.generate_material_class('_'.join(self._objs_prims(args))) | ||
78 | return cls(sum(map(lambda obj: obj.mass, args))) | 76 | return cls(sum(map(lambda obj: obj.mass, args))) | ||
79 | 77 | ||||
80 | @classmethod | 78 | @classmethod | ||
81 | def generate_material_class(cls, name, density=None): | 79 | def generate_material_class(cls, name, density=None): | ||
82 | """Generate a new class if not existing, else return existing.""" | 80 | """Generate a new class if not existing, else return existing.""" | ||
83 | if name not in cls._classes: | 81 | if name not in cls._classes: | ||
84 | prims = map(cls._classes.__getitem__, name.split('_')) | 82 | prims = map(cls._classes.__getitem__, name.split('_')) | ||
85 | density = density or mean(map(lambda obj: obj.density, prims)) | 83 | density = density or mean(map(lambda obj: obj.density, prims)) | ||
86 | cls._classes[name] = type(name, (Material,), {'density': density}) | 84 | cls._classes[name] = type(name, (Material,), {'density': density}) | ||
87 | return cls._classes[name] | 85 | return cls._classes[name] | ||
88 | 86 | ||||
89 | @property | 87 | @property | ||
90 | def volume(self): | 88 | def volume(self): | ||
91 | """Volume generated by this factory.""" | 89 | """Volume generated by this factory.""" | ||
92 | return sum(filter(lambda prd: not prd.obsolete, self._products)) | 90 | return sum(filter(lambda prd: not prd.obsolete, self._products)) | ||
93 | 91 | ||||
94 | @classmethod | 92 | @classmethod | ||
95 | def can_build_together(cls, volume): | 93 | def can_build_together(cls, volume): | ||
96 | """Whether can build a wall from all factories' volume.""" | 94 | """Whether can build a wall from all factories' volume.""" | ||
97 | return sum(cls._factories) >= volume | 95 | return sum(cls._factories) >= volume | ||
98 | 96 | ||||
99 | def can_build(self, volume): | 97 | def can_build(self, volume): | ||
100 | """Whether can build a wall from this factory's volume.""" | 98 | """Whether can build a wall from this factory's volume.""" | ||
101 | return self.volume >= volume | 99 | return self.volume >= volume | ||
102 | 100 | ||||
103 | _PRIMITIVES = {'Concrete': 2500, 'Brick': 2000, | 101 | _PRIMITIVES = {'Concrete': 2500, 'Brick': 2000, | ||
104 | 'Stone': 1600, 'Wood': 600, 'Steel': 7700} | 102 | 'Stone': 1600, 'Wood': 600, 'Steel': 7700} | ||
105 | Concrete, Brick, Stone, Wood, Steel = starmap(Factory.generate_material_class, | 103 | Concrete, Brick, Stone, Wood, Steel = starmap(Factory.generate_material_class, | ||
106 | _PRIMITIVES.items()) | 104 | _PRIMITIVES.items()) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
n | 1 | from itertools import chain | n | 1 | from itertools import chain, starmap |
2 | from statistics import mean | 2 | from statistics import mean | ||
3 | 3 | ||||
4 | 4 | ||||
5 | class AddByVolumeMixin: | 5 | class AddByVolumeMixin: | ||
n | n | 6 | """Allow adding isntances by their volume.""" | ||
6 | 7 | ||||
n | 7 | def __add__(self, other): | n | 8 | __radd__ = lambda self, other: self.volume + getattr(other, 'volume', other) |
8 | if isinstance(other, type(self)): | ||||
9 | return self.volume + other.volume | ||||
10 | return self.volume + other | ||||
11 | __radd__ = __add__ | ||||
12 | 9 | ||||
13 | 10 | ||||
14 | class Material(AddByVolumeMixin): | 11 | class Material(AddByVolumeMixin): | ||
n | 15 | n | 12 | """Represent a material.""" | |
16 | density = ... | ||||
17 | 13 | ||||
18 | def __init__(self, mass): | 14 | def __init__(self, mass): | ||
19 | self.mass = mass | 15 | self.mass = mass | ||
20 | self.obsolete = False | 16 | self.obsolete = False | ||
21 | 17 | ||||
n | n | 18 | __radd__ = lambda self, other: self.volume + getattr(other, 'volume', other) | ||
19 | |||||
20 | def use(self): | ||||
21 | """Ensure not used so far and make obsolete.""" | ||||
22 | assert not self.obsolete and not setattr(self, 'obsolete', True) | ||||
23 | |||||
22 | @property | 24 | @property | ||
23 | def volume(self): | 25 | def volume(self): | ||
n | n | 26 | """Get volume of an instance.""" | ||
24 | return self.mass / self.density | 27 | return self.mass / self.density | ||
25 | 28 | ||||
26 | @property | 29 | @property | ||
27 | def primitive_cls_names(self): | 30 | def primitive_cls_names(self): | ||
n | n | 31 | """Get list of primitive class names used by this instance's class.""" | ||
28 | return self.__class__.__name__.split('_') | 32 | return self.__class__.__name__.split('_') | ||
29 | 33 | ||||
30 | 34 | ||||
n | 31 | class Concrete(Material): | n | 35 | class Factory(AddByVolumeMixin): |
32 | density = 2500 | 36 | """Factory that generates materials.""" | ||
33 | 37 | ||||
n | 34 | n | 38 | _factories = [] # All Factory instances | |
35 | class Brick(Material): | 39 | _classes = {} # All known material types behind their name as str | ||
36 | density = 2000 | ||||
37 | |||||
38 | |||||
39 | class Stone(Material): | ||||
40 | density = 1600 | ||||
41 | |||||
42 | |||||
43 | class Wood(Material): | ||||
44 | density = 600 | ||||
45 | |||||
46 | |||||
47 | class Steel(Material): | ||||
48 | density = 7700 | ||||
49 | |||||
50 | |||||
51 | class Factory(AddByVolumeMixin): | ||||
52 | |||||
53 | _factories = [] | ||||
54 | _classes = {cls.__name__: cls for cls in (Concrete, Brick, | ||||
55 | Stone, Wood, Steel)} | ||||
56 | 40 | ||||
57 | def __init__(self): | 41 | def __init__(self): | ||
58 | self._factories.append(self) | 42 | self._factories.append(self) | ||
59 | self._products = [] | 43 | self._products = [] | ||
60 | 44 | ||||
n | n | 45 | @staticmethod | ||
46 | def _store_output(fun): | ||||
47 | """Store objects getting out for future reference.""" | ||||
48 | def decorated(self, *args, **kwargs): | ||||
49 | result = fun(self, *args, **kwargs) | ||||
50 | self._products.extend(result if type(result) is tuple else [result]) | ||||
51 | return result | ||||
52 | return decorated | ||||
53 | |||||
54 | @_store_output | ||||
61 | def __call__(self, *args, **kwargs): | 55 | def __call__(self, *args, **kwargs): | ||
n | n | 56 | """Create new materials.""" | ||
62 | if not bool(args) ^ bool(kwargs): | 57 | if not bool(args) ^ bool(kwargs): | ||
63 | raise ValueError | 58 | raise ValueError | ||
64 | if kwargs: | 59 | if kwargs: | ||
65 | return tuple(self._from_classnames(**kwargs)) | 60 | return tuple(self._from_classnames(**kwargs)) | ||
n | 66 | self._handle_obsolete_objects(args) | n | 61 | [obj.use() for obj in args] # Make materials obsolete |
67 | return self._from_products(*args) | 62 | return self._from_products(*args) | ||
68 | 63 | ||||
69 | @staticmethod | 64 | @staticmethod | ||
n | 70 | def _primitives_from_objects(objects): | n | 65 | def _objs_prims(objects): |
66 | """Get a sorted list of all promitives' names from a list of objects.""" | ||||
71 | return sorted(chain(*map(lambda arg: arg.primitive_cls_names, objects))) | 67 | return sorted(chain(*map(lambda obj: obj.primitive_cls_names, objects))) | ||
72 | |||||
73 | @staticmethod | ||||
74 | def _handle_obsolete_objects(objects): | ||||
75 | for obj in objects: | ||||
76 | if obj.obsolete: | ||||
77 | raise AssertionError | ||||
78 | obj.obsolete = True | ||||
79 | 68 | ||||
80 | def _from_classnames(self, **kwargs): | 69 | def _from_classnames(self, **kwargs): | ||
n | 81 | for cls_name, mass in kwargs.items(): | n | 70 | """Create materials from classnames (instance called with kwargs).""" |
82 | try: | 71 | if not set(kwargs.keys()).issubset(set(self._classes)): | ||
83 | material = self._classes[cls_name](mass) | 72 | raise ValueError | ||
84 | except KeyError: | 73 | yield from (self._classes[name](mass) for name, mass in kwargs.items()) | ||
85 | raise ValueError # Unknown class requested | ||||
86 | self._products.append(material) | ||||
87 | yield material | ||||
88 | |||||
89 | def _class_from_class_names(self, cls_name): | ||||
90 | if cls_name in self._classes: | ||||
91 | return self._classes[cls_name] | ||||
92 | primitives = list(map(self._classes.__getitem__, cls_name.split('_'))) | ||||
93 | avg_dencity = mean(map(lambda obj: obj.density, primitives)) | ||||
94 | cls = type(cls_name, (Material,), {'density': avg_dencity}) | ||||
95 | self._classes[cls_name] = cls | ||||
96 | return cls | ||||
97 | 74 | ||||
98 | def _from_products(self, *args): | 75 | def _from_products(self, *args): | ||
n | 99 | primitives_cls_names = self._primitives_from_objects(args) | n | 76 | """Create materials from instances (instance called with args).""" |
100 | cls_name = '_'.join(primitives_cls_names) | 77 | cls = self.generate_material_class('_'.join(self._objs_prims(args))) | ||
101 | cls = self._class_from_class_names(cls_name) | ||||
102 | mass = sum(map(lambda obj: obj.mass, args)) | 78 | return cls(sum(map(lambda obj: obj.mass, args))) | ||
103 | self._products.append(material:=cls(mass)) | 79 | |||
104 | return material | 80 | @classmethod | ||
81 | def generate_material_class(cls, name, density=None): | ||||
82 | """Generate a new class if not existing, else return existing.""" | ||||
83 | if name not in cls._classes: | ||||
84 | prims = map(cls._classes.__getitem__, name.split('_')) | ||||
85 | density = density or mean(map(lambda obj: obj.density, prims)) | ||||
86 | cls._classes[name] = type(name, (Material,), {'density': density}) | ||||
87 | return cls._classes[name] | ||||
105 | 88 | ||||
106 | @property | 89 | @property | ||
107 | def volume(self): | 90 | def volume(self): | ||
n | n | 91 | """Volume generated by this factory.""" | ||
108 | return sum(filter(lambda prd: not prd.obsolete, self._products)) | 92 | return sum(filter(lambda prd: not prd.obsolete, self._products)) | ||
109 | 93 | ||||
110 | @classmethod | 94 | @classmethod | ||
111 | def can_build_together(cls, volume): | 95 | def can_build_together(cls, volume): | ||
n | n | 96 | """Whether can build a wall from all factories' volume.""" | ||
112 | return sum(cls._factories) >= volume | 97 | return sum(cls._factories) >= volume | ||
113 | 98 | ||||
114 | def can_build(self, volume): | 99 | def can_build(self, volume): | ||
n | n | 100 | """Whether can build a wall from this factory's volume.""" | ||
115 | return self.volume >= volume | 101 | return self.volume >= volume | ||
t | t | 102 | |||
103 | _PRIMITIVES = {'Concrete': 2500, 'Brick': 2000, | ||||
104 | 'Stone': 1600, 'Wood': 600, 'Steel': 7700} | ||||
105 | Concrete, Brick, Stone, Wood, Steel = starmap(Factory.generate_material_class, | ||||
106 | _PRIMITIVES.items()) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
26.11.2024 17:55