1import gc
  2import re
  3
  4
  5class SingletonMeta(type):
  6    _instances = {}
  7
  8    def __call__(cls, *args, **kwargs):
  9        if cls not in cls._instances:
 10            cls._instances[cls] = super().__call__(*args, **kwargs)
 11        return cls._instances[cls]
 12    
 13
 14class Santa(metaclass=SingletonMeta):
 15
 16    _kids_dict = {}
 17    _wanted_gifts = 0
 18
 19    def __call__(self, kid_instance, present_str):
 20        gift_pattern = r"(['\"])([A-Za-z0-9\s]+)\1"
 21        match = re.search(gift_pattern, present_str)
 22
 23        if match:
 24            gift = match.group(2)
 25            self._kids_dict[id(kid_instance)]["present"] = gift
 26            self._wanted_gifts += 1
 27
 28    def __matmul__(self, letter):
 29        gift_pattern = r"(['\"])([A-Za-z0-9\s]+?)\1"
 30        gift_match = re.search(gift_pattern, letter)
 31        if gift_match:
 32            gift = gift_match.group(2)
 33
 34            signature_pattern = r"^\s*(\d+)\s*$"
 35            signature_match = re.search(
 36                signature_pattern, letter.strip().splitlines()[-1]
 37            )
 38
 39            if signature_match:
 40                child_id = signature_match.group(1)
 41                self._kids_dict[int(child_id)]["present"] = gift
 42                self._wanted_gifts += 1
 43
 44    def __iter__(self):
 45        value = True
 46        for kid in self._kids_dict.items():
 47            kid_id = kid[0]
 48            if self._kids_dict[kid_id]["present"]:
 49               value = False 
 50
 51        if value:
 52            return
 53
 54        for key, value in self._kids_dict.items():
 55            yield value["present"]
 56
 57    @staticmethod
 58    def add_kid(kid_class):
 59        Santa._kids_dict[id(kid_class)] = {"age": 0, "error_cnt": 0, "present": None}
 60
 61    @staticmethod
 62    def add_error(kid_class, error):
 63        Santa._kids_dict[id(kid_class)]["error_cnt"] += 1
 64
 65    def xmas(self):
 66        for kid in self._kids_dict.items():
 67            kid_id = kid[0]
 68            self._kids_dict[kid_id]["age"] += 1
 69
 70        if self._wanted_gifts < 1:
 71            return
 72        self._wanted_gifts = 0
 73
 74        for kid in self._kids_dict.items():
 75            kid_id = kid[0]
 76            if self._kids_dict[kid_id]["age"] > 5:
 77                continue
 78
 79            if self._kids_dict[kid_id]["error_cnt"] > 0:
 80                kid = get_instance_by_id(kid_id)
 81                kid("coal")
 82                self._kids_dict[kid_id]["error_cnt"] = 0
 83                continue
 84
 85            if kid[1]["present"] is not None:
 86                kid_instance = get_instance_by_id(kid_id)
 87                kid_instance(kid[1]["present"])
 88                continue
 89
 90            count_sort_arr = {}
 91            for present in self:
 92                if present is not None:
 93                    if present is not count_sort_arr:
 94                        count_sort_arr[present] = 0
 95                    else:
 96                        count_sort_arr[present] += 1
 97            most_frequent = max(count_sort_arr, key=count_sort_arr.get)
 98            kid_instance = get_instance_by_id(kid_id)
 99            kid_instance(most_frequent)
100
101            self._kids_dict[kid_id]["age"] += 1
102
103        for kid in self._kids_dict.items():
104            kid_id = kid[0]
105            self._kids_dict[kid_id]["present"] = None
106
107def get_instance_by_id(obj_id):
108    for obj in gc.get_objects():
109        if id(obj) == obj_id:
110            return obj
111    return None
112
113
114class Kid(type):
115    def __call__(cls, *args, **kwargs):
116        instance = super().__call__(*args, **kwargs)
117        Santa.add_kid(instance)
118        return instance
119
120    def __new__(cls, name, bases, dct):
121
122        if "__call__" not in dct:
123            raise NotImplementedError
124
125        for attr_name, attr_value in dct.items():
126            if (
127                callable(attr_value)
128                and not attr_name.startswith("__")
129                and not attr_name.startswith("_")
130            ):
131
132                def wrapper(self, *args, **kwargs):
133                    try:
134                        return attr_value(self, *args, **kwargs)
135                    except Exception as e:
136                        Santa.add_error(self, e)
137                        raise e
138
139                dct[attr_name] = wrapper
140        return super().__new__(cls, name, bases, dct)
......FF.....E...E..
======================================================================
ERROR: test_xmass_naughty (test.TestSanta.test_xmass_naughty)
Test a Christmas with naughty kids.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 223, in test_xmass_naughty
    kid1.public_with_error()
  File "/tmp/solution.py", line 137, in wrapper
    raise e
  File "/tmp/solution.py", line 134, in wrapper
    return attr_value(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/test.py", line 30, in <lambda>
    '_private_with_error': lambda self: not_existing,
                                        ^^^^^^^^^^^^
NameError: name 'not_existing' is not defined
======================================================================
ERROR: test_xmass_public_with_no_error (test.TestSanta.test_xmass_public_with_no_error)
Test a Christmas with not-so-naughty kids.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 246, in test_xmass_public_with_no_error
    self.assertEqual(kid1.public_without_error(), 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/solution.py", line 137, in wrapper
    raise e
  File "/tmp/solution.py", line 134, in wrapper
    return attr_value(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/test.py", line 30, in <lambda>
    '_private_with_error': lambda self: not_existing,
                                        ^^^^^^^^^^^^
NameError: name 'not_existing' is not defined
======================================================================
FAIL: test_present_matching (test.TestSanta.test_present_matching)
Test matching signature in the letter.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 92, in test_present_matching
    self.assertEqual(list(self.santa), ['toy4', 'abcdefgHIJKLMNopQRstUVwxYZ 1 2 3 4567890   '])
AssertionError: Lists differ: ['toy4', None] != ['toy4', 'abcdefgHIJKLMNopQRstUVwxYZ 1 2 3 4567890   ']
First differing element 1:
None
'abcdefgHIJKLMNopQRstUVwxYZ 1 2 3 4567890   '
- ['toy4', None]
+ ['toy4', 'abcdefgHIJKLMNopQRstUVwxYZ 1 2 3 4567890   ']
======================================================================
FAIL: test_santa_gift_order (test.TestSanta.test_santa_gift_order)
Test ordering of the Santa iterator.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 260, in test_santa_gift_order
    self.assertEqual(list(self.santa), ["toy2v2", "toy3", "toy1"])
AssertionError: Lists differ: ['toy1', 'toy2v2', 'toy3'] != ['toy2v2', 'toy3', 'toy1']
First differing element 0:
'toy1'
'toy2v2'
- ['toy1', 'toy2v2', 'toy3']
+ ['toy2v2', 'toy3', 'toy1']
----------------------------------------------------------------------
Ran 20 tests in 0.074s
FAILED (failures=2, errors=2)
| f | 1 | import gc | f | 1 | import gc | 
| 2 | import re | 2 | import re | ||
| 3 | 3 | ||||
| 4 | 4 | ||||
| 5 | class SingletonMeta(type): | 5 | class SingletonMeta(type): | ||
| 6 | _instances = {} | 6 | _instances = {} | ||
| 7 | 7 | ||||
| 8 | def __call__(cls, *args, **kwargs): | 8 | def __call__(cls, *args, **kwargs): | ||
| 9 | if cls not in cls._instances: | 9 | if cls not in cls._instances: | ||
| 10 | cls._instances[cls] = super().__call__(*args, **kwargs) | 10 | cls._instances[cls] = super().__call__(*args, **kwargs) | ||
| 11 | return cls._instances[cls] | 11 | return cls._instances[cls] | ||
| n | 12 | n | 12 | ||
| 13 | 13 | ||||
| 14 | class Santa(metaclass=SingletonMeta): | 14 | class Santa(metaclass=SingletonMeta): | ||
| 15 | 15 | ||||
| 16 | _kids_dict = {} | 16 | _kids_dict = {} | ||
| 17 | _wanted_gifts = 0 | 17 | _wanted_gifts = 0 | ||
| 18 | 18 | ||||
| n | 19 | def __call__(self, kid_class, present_str): | n | 19 | def __call__(self, kid_instance, present_str): | 
| 20 | gift_pattern = r"(['\"])([A-Za-z0-9\s]+)\1" | 20 | gift_pattern = r"(['\"])([A-Za-z0-9\s]+)\1" | ||
| 21 | match = re.search(gift_pattern, present_str) | 21 | match = re.search(gift_pattern, present_str) | ||
| 22 | 22 | ||||
| 23 | if match: | 23 | if match: | ||
| 24 | gift = match.group(2) | 24 | gift = match.group(2) | ||
| n | 25 | self._kids_dict[id(kid_class)]["present"] = gift | n | 25 | self._kids_dict[id(kid_instance)]["present"] = gift | 
| 26 | self._wanted_gifts += 1 | 26 | self._wanted_gifts += 1 | ||
| 27 | 27 | ||||
| 28 | def __matmul__(self, letter): | 28 | def __matmul__(self, letter): | ||
| 29 | gift_pattern = r"(['\"])([A-Za-z0-9\s]+?)\1" | 29 | gift_pattern = r"(['\"])([A-Za-z0-9\s]+?)\1" | ||
| 30 | gift_match = re.search(gift_pattern, letter) | 30 | gift_match = re.search(gift_pattern, letter) | ||
| 31 | if gift_match: | 31 | if gift_match: | ||
| 32 | gift = gift_match.group(2) | 32 | gift = gift_match.group(2) | ||
| 33 | 33 | ||||
| 34 | signature_pattern = r"^\s*(\d+)\s*$" | 34 | signature_pattern = r"^\s*(\d+)\s*$" | ||
| 35 | signature_match = re.search( | 35 | signature_match = re.search( | ||
| 36 | signature_pattern, letter.strip().splitlines()[-1] | 36 | signature_pattern, letter.strip().splitlines()[-1] | ||
| 37 | ) | 37 | ) | ||
| 38 | 38 | ||||
| 39 | if signature_match: | 39 | if signature_match: | ||
| 40 | child_id = signature_match.group(1) | 40 | child_id = signature_match.group(1) | ||
| 41 | self._kids_dict[int(child_id)]["present"] = gift | 41 | self._kids_dict[int(child_id)]["present"] = gift | ||
| 42 | self._wanted_gifts += 1 | 42 | self._wanted_gifts += 1 | ||
| 43 | 43 | ||||
| 44 | def __iter__(self): | 44 | def __iter__(self): | ||
| n | n | 45 | value = True | ||
| 46 | for kid in self._kids_dict.items(): | ||||
| 47 | kid_id = kid[0] | ||||
| 48 | if self._kids_dict[kid_id]["present"]: | ||||
| 49 | value = False | ||||
| 50 | |||||
| 51 | if value: | ||||
| 52 | return | ||||
| 53 | |||||
| 45 | for key, value in self._kids_dict.items(): | 54 | for key, value in self._kids_dict.items(): | ||
| 46 | yield value["present"] | 55 | yield value["present"] | ||
| 47 | 56 | ||||
| 48 | @staticmethod | 57 | @staticmethod | ||
| 49 | def add_kid(kid_class): | 58 | def add_kid(kid_class): | ||
| 50 | Santa._kids_dict[id(kid_class)] = {"age": 0, "error_cnt": 0, "present": None} | 59 | Santa._kids_dict[id(kid_class)] = {"age": 0, "error_cnt": 0, "present": None} | ||
| 51 | 60 | ||||
| 52 | @staticmethod | 61 | @staticmethod | ||
| 53 | def add_error(kid_class, error): | 62 | def add_error(kid_class, error): | ||
| 54 | Santa._kids_dict[id(kid_class)]["error_cnt"] += 1 | 63 | Santa._kids_dict[id(kid_class)]["error_cnt"] += 1 | ||
| 55 | 64 | ||||
| 56 | def xmas(self): | 65 | def xmas(self): | ||
| 57 | for kid in self._kids_dict.items(): | 66 | for kid in self._kids_dict.items(): | ||
| 58 | kid_id = kid[0] | 67 | kid_id = kid[0] | ||
| 59 | self._kids_dict[kid_id]["age"] += 1 | 68 | self._kids_dict[kid_id]["age"] += 1 | ||
| 60 | 69 | ||||
| 61 | if self._wanted_gifts < 1: | 70 | if self._wanted_gifts < 1: | ||
| 62 | return | 71 | return | ||
| 63 | self._wanted_gifts = 0 | 72 | self._wanted_gifts = 0 | ||
| 64 | 73 | ||||
| 65 | for kid in self._kids_dict.items(): | 74 | for kid in self._kids_dict.items(): | ||
| 66 | kid_id = kid[0] | 75 | kid_id = kid[0] | ||
| n | 67 | if self._kids_dict[kid_id]["age"] > 6: | n | 76 | if self._kids_dict[kid_id]["age"] > 5: | 
| 68 | continue | 77 | continue | ||
| 69 | 78 | ||||
| 70 | if self._kids_dict[kid_id]["error_cnt"] > 0: | 79 | if self._kids_dict[kid_id]["error_cnt"] > 0: | ||
| 71 | kid = get_instance_by_id(kid_id) | 80 | kid = get_instance_by_id(kid_id) | ||
| 72 | kid("coal") | 81 | kid("coal") | ||
| 73 | self._kids_dict[kid_id]["error_cnt"] = 0 | 82 | self._kids_dict[kid_id]["error_cnt"] = 0 | ||
| 74 | continue | 83 | continue | ||
| 75 | 84 | ||||
| 76 | if kid[1]["present"] is not None: | 85 | if kid[1]["present"] is not None: | ||
| 77 | kid_instance = get_instance_by_id(kid_id) | 86 | kid_instance = get_instance_by_id(kid_id) | ||
| 78 | kid_instance(kid[1]["present"]) | 87 | kid_instance(kid[1]["present"]) | ||
| 79 | continue | 88 | continue | ||
| 80 | 89 | ||||
| 81 | count_sort_arr = {} | 90 | count_sort_arr = {} | ||
| 82 | for present in self: | 91 | for present in self: | ||
| 83 | if present is not None: | 92 | if present is not None: | ||
| 84 | if present is not count_sort_arr: | 93 | if present is not count_sort_arr: | ||
| 85 | count_sort_arr[present] = 0 | 94 | count_sort_arr[present] = 0 | ||
| 86 | else: | 95 | else: | ||
| 87 | count_sort_arr[present] += 1 | 96 | count_sort_arr[present] += 1 | ||
| 88 | most_frequent = max(count_sort_arr, key=count_sort_arr.get) | 97 | most_frequent = max(count_sort_arr, key=count_sort_arr.get) | ||
| 89 | kid_instance = get_instance_by_id(kid_id) | 98 | kid_instance = get_instance_by_id(kid_id) | ||
| 90 | kid_instance(most_frequent) | 99 | kid_instance(most_frequent) | ||
| 91 | 100 | ||||
| 92 | self._kids_dict[kid_id]["age"] += 1 | 101 | self._kids_dict[kid_id]["age"] += 1 | ||
| 93 | 102 | ||||
| 94 | for kid in self._kids_dict.items(): | 103 | for kid in self._kids_dict.items(): | ||
| 95 | kid_id = kid[0] | 104 | kid_id = kid[0] | ||
| 96 | self._kids_dict[kid_id]["present"] = None | 105 | self._kids_dict[kid_id]["present"] = None | ||
| 97 | 106 | ||||
| n | 98 | n | |||
| 99 | def get_instance_by_id(obj_id): | 107 | def get_instance_by_id(obj_id): | ||
| 100 | for obj in gc.get_objects(): | 108 | for obj in gc.get_objects(): | ||
| 101 | if id(obj) == obj_id: | 109 | if id(obj) == obj_id: | ||
| 102 | return obj | 110 | return obj | ||
| 103 | return None | 111 | return None | ||
| 104 | 112 | ||||
| 105 | 113 | ||||
| 106 | class Kid(type): | 114 | class Kid(type): | ||
| 107 | def __call__(cls, *args, **kwargs): | 115 | def __call__(cls, *args, **kwargs): | ||
| 108 | instance = super().__call__(*args, **kwargs) | 116 | instance = super().__call__(*args, **kwargs) | ||
| 109 | Santa.add_kid(instance) | 117 | Santa.add_kid(instance) | ||
| 110 | return instance | 118 | return instance | ||
| 111 | 119 | ||||
| 112 | def __new__(cls, name, bases, dct): | 120 | def __new__(cls, name, bases, dct): | ||
| n | n | 121 | |||
| 122 | if "__call__" not in dct: | ||||
| 123 | raise NotImplementedError | ||||
| 124 | |||||
| 113 | for attr_name, attr_value in dct.items(): | 125 | for attr_name, attr_value in dct.items(): | ||
| 114 | if ( | 126 | if ( | ||
| 115 | callable(attr_value) | 127 | callable(attr_value) | ||
| 116 | and not attr_name.startswith("__") | 128 | and not attr_name.startswith("__") | ||
| 117 | and not attr_name.startswith("_") | 129 | and not attr_name.startswith("_") | ||
| 118 | ): | 130 | ): | ||
| 119 | 131 | ||||
| 120 | def wrapper(self, *args, **kwargs): | 132 | def wrapper(self, *args, **kwargs): | ||
| 121 | try: | 133 | try: | ||
| 122 | return attr_value(self, *args, **kwargs) | 134 | return attr_value(self, *args, **kwargs) | ||
| 123 | except Exception as e: | 135 | except Exception as e: | ||
| 124 | Santa.add_error(self, e) | 136 | Santa.add_error(self, e) | ||
| 125 | raise e | 137 | raise e | ||
| 126 | 138 | ||||
| 127 | dct[attr_name] = wrapper | 139 | dct[attr_name] = wrapper | ||
| 128 | return super().__new__(cls, name, bases, dct) | 140 | return super().__new__(cls, name, bases, dct) | ||
| t | 129 | t | 
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| 
 | 
 | |||||||||