Домашни > Another brick in the wall > Решения > Решението на Георги Кунчев

Резултати
9 точки от тестове
0 точки от учител

9 точки общо

9 успешни теста
1 неуспешни теста
Код
Скрий всички коментари

  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)

Дискусия
История

n1class Material:n1from 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 
2    def __init__(self, mass):14    def __init__(self, mass):
n3        self._mass = massn15        self.mass = mass
4        self._used = False16        self.obsolete = False
517
n6    @propertyn
7    def mass(self):18    def use(self):
8        return self._mass19        """Ensure not used so far and make obsolete."""
20        assert not self.obsolete and not setattr(self, 'obsolete', True)
921
10    @property22    @property
11    def volume(self):23    def volume(self):
nn24        """Get volume of an instance."""
12        return float(self._mass) / self.density25        return self.mass / self.density
1326
n14class Concrete(Material):n27    @property
15    density = 250028    def primitive_cls_names(self):
29        """Get list of primitive class names used by this instance's class."""
30        return self.__class__.__name__.split('_')
1631
n17class Brick(Material):n
18    density = 2000
1932
n20class Stone(Material):n33class Factory(AddByVolumeMixin):
21    density = 160034    """Factory that generates materials."""
2235
n23class Wood(Material):n36    _factories = []  # All Factory instances
24    density = 60037    _classes = {}  # All known material types behind their name as str
25 
26class Steel(Material):
27    density = 7700
28 
29class Factory:
30    _created_materials = []
31    _alloy_classes = {}
3238
33    def __init__(self):39    def __init__(self):
nn40        self._factories.append(self)
34        self._materials = []41        self._products = []
3542
nn43    @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):
n37        if not args and not kwargs:n54        """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
3960
n40        if args and kwargs:n61        return self._from_products(*args)
41            raise ValueError("Cannot mix positional and keyword arguments")
4262
n43        if kwargs:n63    @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)))
4567
n46        return self._handle_args(args)n68    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())
4773
n48    def _handle_kwargs(self, kwargs):n74    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)
5678
n57    def _handle_args(self, args):n79    @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 = True83            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]
6287
n63        base_classes = self._get_base_classes(args)n88    @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
9992
100    @classmethod93    @classmethod
101    def can_build_together(cls, volume):94    def can_build_together(cls, volume):
t102        total_volume = sum(m.volume for m in cls._created_materials if not getattr(m, '_used', False))t95        """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 >= volume100        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())
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

n1from itertools import chain, starmapn1class Material:
2from statistics import mean2    def __init__(self, mass):
3        self._mass = mass
4        self._used = False
35
n4 n6    @property
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):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)
219
22    @property10    @property
23    def volume(self):11    def volume(self):
n24        """Get volume of an instance."""n
25        return self.mass / self.density12        return float(self._mass) / self.density
2613
n27    @propertyn14class 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('_')
3116
nn17class Brick(Material):
18    density = 2000
3219
n33class Factory(AddByVolumeMixin):n20class Stone(Material):
34    """Factory that generates materials."""21    density = 1600
3522
n36    _factories = []  # All Factory instancesn23class Wood(Material):
37    _classes = {}  # All known material types behind their name as str24    density = 600
25 
26class Steel(Material):
27    density = 7700
28 
29class Factory:
30    _created_materials = []
31    _alloy_classes = {}
3832
39    def __init__(self):33    def __init__(self):
n40        self._factories.append(self)n
41        self._products = []34        self._materials = []
4235
n43    @staticmethodn
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
5139
n52    @_store_outputn40        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:
n58            return tuple(self._from_classnames(**kwargs))n44            return self._handle_kwargs(kwargs)
59        [obj.use() for obj in args]  # Make materials obsolete
60        return self._from_products(*args)
6145
n62    @staticmethodn46        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)))
6647
n67    def _from_classnames(self, **kwargs):n48    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 ValueError51            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)
7256
n73    def _from_products(self, *args):n57    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
7762
n78    @classmethodn63        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]
8665
n87    @propertyn66        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
9199
92    @classmethod100    @classmethod
93    def can_build_together(cls, volume):101    def can_build_together(cls, volume):
t94        """Whether can build a wall from all factories' volume."""t102        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 >= volume103        return total_volume >= volume
100 
101 
102_PRIMITIVES = {'Concrete': 2500, 'Brick': 2000,
103               'Stone': 1600, 'Wood': 600, 'Steel': 7700}
104Concrete, Brick, Stone, Wood, Steel = starmap(Factory.generate_material_class,
105                                              _PRIMITIVES.items())
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

f1from itertools import chain, starmapf1from itertools import chain, starmap
2from statistics import mean2from statistics import mean
33
44
5class AddByVolumeMixin:5class AddByVolumeMixin:
6    """Allow adding isntances by their volume."""6    """Allow adding isntances by their volume."""
77
8    __radd__ = lambda self, other: self.volume + getattr(other, 'volume', other)8    __radd__ = lambda self, other: self.volume + getattr(other, 'volume', other)
99
1010
11class Material(AddByVolumeMixin):11class Material(AddByVolumeMixin):
12    """Represent a material."""12    """Represent a material."""
1313
14    def __init__(self, mass):14    def __init__(self, mass):
15        self.mass = mass15        self.mass = mass
16        self.obsolete = False16        self.obsolete = False
1717
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)
2121
22    @property22    @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.density25        return self.mass / self.density
2626
27    @property27    @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('_')
3131
3232
33class Factory(AddByVolumeMixin):33class Factory(AddByVolumeMixin):
34    """Factory that generates materials."""34    """Factory that generates materials."""
3535
36    _factories = []  # All Factory instances36    _factories = []  # All Factory instances
37    _classes = {}  # All known material types behind their name as str37    _classes = {}  # All known material types behind their name as str
3838
39    def __init__(self):39    def __init__(self):
40        self._factories.append(self)40        self._factories.append(self)
41        self._products = []41        self._products = []
4242
43    @staticmethod43    @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 result49            return result
50        return decorated50        return decorated
5151
52    @_store_output52    @_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 ValueError56            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 obsolete59        [obj.use() for obj in args]  # Make materials obsolete
60        return self._from_products(*args)60        return self._from_products(*args)
6161
62    @staticmethod62    @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)))
6666
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 ValueError70            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())
7272
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)))
7777
78    @classmethod78    @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]
8686
87    @property87    @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))
9191
92    @classmethod92    @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) >= volume95        return sum(cls._factories) >= volume
9696
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 >= volume99        return self.volume >= volume
100100
tt101 
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}
103Concrete, Brick, Stone, Wood, Steel = starmap(Factory.generate_material_class,104Concrete, Brick, Stone, Wood, Steel = starmap(Factory.generate_material_class,
104                                              _PRIMITIVES.items())105                                              _PRIMITIVES.items())
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

f1from itertools import chain, starmapf1from itertools import chain, starmap
2from statistics import mean2from statistics import mean
33
44
5class AddByVolumeMixin:5class AddByVolumeMixin:
6    """Allow adding isntances by their volume."""6    """Allow adding isntances by their volume."""
77
8    __radd__ = lambda self, other: self.volume + getattr(other, 'volume', other)8    __radd__ = lambda self, other: self.volume + getattr(other, 'volume', other)
99
1010
11class Material(AddByVolumeMixin):11class Material(AddByVolumeMixin):
12    """Represent a material."""12    """Represent a material."""
1313
14    def __init__(self, mass):14    def __init__(self, mass):
15        self.mass = mass15        self.mass = mass
16        self.obsolete = False16        self.obsolete = False
t17 t
18    __radd__ = lambda self, other: self.volume + getattr(other, 'volume', other)
1917
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)
2321
24    @property22    @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.density25        return self.mass / self.density
2826
29    @property27    @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('_')
3331
3432
35class Factory(AddByVolumeMixin):33class Factory(AddByVolumeMixin):
36    """Factory that generates materials."""34    """Factory that generates materials."""
3735
38    _factories = []  # All Factory instances36    _factories = []  # All Factory instances
39    _classes = {}  # All known material types behind their name as str37    _classes = {}  # All known material types behind their name as str
4038
41    def __init__(self):39    def __init__(self):
42        self._factories.append(self)40        self._factories.append(self)
43        self._products = []41        self._products = []
4442
45    @staticmethod43    @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 result49            return result
52        return decorated50        return decorated
5351
54    @_store_output52    @_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 ValueError56            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 obsolete59        [obj.use() for obj in args]  # Make materials obsolete
62        return self._from_products(*args)60        return self._from_products(*args)
6361
64    @staticmethod62    @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)))
6866
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 ValueError70            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())
7472
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)))
7977
80    @classmethod78    @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]
8886
89    @property87    @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))
9391
94    @classmethod92    @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) >= volume95        return sum(cls._factories) >= volume
9896
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 >= volume99        return self.volume >= volume
102100
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}
105Concrete, Brick, Stone, Wood, Steel = starmap(Factory.generate_material_class,103Concrete, Brick, Stone, Wood, Steel = starmap(Factory.generate_material_class,
106                                              _PRIMITIVES.items())104                                              _PRIMITIVES.items())
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

n1from itertools import chainn1from itertools import chain, starmap
2from statistics import mean2from statistics import mean
33
44
5class AddByVolumeMixin:5class AddByVolumeMixin:
nn6    """Allow adding isntances by their volume."""
67
n7    def __add__(self, other):n8    __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__
129
1310
14class Material(AddByVolumeMixin):11class Material(AddByVolumeMixin):
n15 n12    """Represent a material."""
16    density = ...
1713
18    def __init__(self, mass):14    def __init__(self, mass):
19        self.mass = mass15        self.mass = mass
20        self.obsolete = False16        self.obsolete = False
2117
nn18    __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    @property24    @property
23    def volume(self):25    def volume(self):
nn26        """Get volume of an instance."""
24        return self.mass / self.density27        return self.mass / self.density
2528
26    @property29    @property
27    def primitive_cls_names(self):30    def primitive_cls_names(self):
nn31        """Get list of primitive class names used by this instance's class."""
28        return self.__class__.__name__.split('_')32        return self.__class__.__name__.split('_')
2933
3034
n31class Concrete(Material):n35class Factory(AddByVolumeMixin):
32    density = 250036    """Factory that generates materials."""
3337
n34 n38    _factories = []  # All Factory instances
35class Brick(Material):39    _classes = {}  # All known material types behind their name as str
36    density = 2000
37 
38 
39class Stone(Material):
40    density = 1600
41 
42 
43class Wood(Material):
44    density = 600
45 
46 
47class Steel(Material):
48    density = 7700
49 
50 
51class Factory(AddByVolumeMixin):
52 
53    _factories = []
54    _classes = {cls.__name__: cls for cls in (Concrete, Brick,
55                                              Stone, Wood, Steel)}
5640
57    def __init__(self):41    def __init__(self):
58        self._factories.append(self)42        self._factories.append(self)
59        self._products = []43        self._products = []
6044
nn45    @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):
nn56        """Create new materials."""
62        if not bool(args) ^ bool(kwargs):57        if not bool(args) ^ bool(kwargs):
63            raise ValueError58            raise ValueError
64        if kwargs:59        if kwargs:
65            return tuple(self._from_classnames(**kwargs))60            return tuple(self._from_classnames(**kwargs))
n66        self._handle_obsolete_objects(args)n61        [obj.use() for obj in args]  # Make materials obsolete
67        return self._from_products(*args)62        return self._from_products(*args)
6863
69    @staticmethod64    @staticmethod
n70    def _primitives_from_objects(objects):n65    def _objs_prims(objects):
66        """Get a sorted list of all promitives' names from a list of objects."""
71        return sorted(chain(*map(lambda argarg.primitive_cls_names, objects)))67        return sorted(chain(*map(lambda objobj.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
7968
80    def _from_classnames(self, **kwargs):69    def _from_classnames(self, **kwargs):
n81        for cls_name, mass in kwargs.items():n70        """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
9774
98    def _from_products(self, *args):75    def _from_products(self, *args):
n99        primitives_cls_names = self._primitives_from_objects(args)n76        """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 material80    @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]
10588
106    @property89    @property
107    def volume(self):90    def volume(self):
nn91        """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))
10993
110    @classmethod94    @classmethod
111    def can_build_together(cls, volume):95    def can_build_together(cls, volume):
nn96        """Whether can build a wall from all factories' volume."""
112        return sum(cls._factories) >= volume97        return sum(cls._factories) >= volume
11398
114    def can_build(self, volume):99    def can_build(self, volume):
nn100        """Whether can build a wall from this factory's volume."""
115        return self.volume >= volume101        return self.volume >= volume
tt102 
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())
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op