Домашни > Подаръци ще има за всички от сърце


Подаръци ще има за всички от сърце
Краен срок: 19.12.2024 18:00
Точки: 10

Идва Коледа. Всички деца трепнат в очакване да получат подаръци, но Дядо Коледа знае всичко и раздава подаръци само на послушните. Да помогнем да Дядо Коледа да се справи. ![дядя](/media/resources/santa.png "Хо-хохо") ### Напишете клас `Santa` * Дядо Коледа е само един, така че класът трябва да е singleton. ``` santa1 = Santa() santa2 = Santa() santa1 is santa2 # True ``` * Дядо Коледа може да получи желания за подаръци по два начина: * Чрез обаждане по телефона: ``` santa = Santa() santa(&lt;дете&gt;, &lt;желание&gt;) ``` Тук "<дете>" е инстанция на клас, чийто метаклас дефинираме по-долу. "<желание>" е от тип `str` и съдържа текст, в който детето описва колко е било послушно и какво иска да получи в замяна. По-долу описваме формата на желанието. * Чрез писмо: ``` santa = Santa() santa @ &lt;писмо&gt; ``` Тук "<писмо>" е от тип `str` и съдържа текст, в който детето описва колко е било послушно и какво иска да получи в замяна. Освен това писмото съдържа подпис на детето. По-долу описваме формата на писмото. Независимо дали съобщението е получено като писмо или чрез обаждане, текстът, който описва съобщението, може да съдържа произволно съдържание. Ако съобщението е в писмо, очаква се някъде в писмото да има подпис. Независимо дали съобщението е от писмо или от обаждане, някъде в него очакваме да има дефинирано желание за подарък. * Подписът се дефинира като ред, в който има само цифри, които могат да са обградени от интервали, табулации и други whitespace символи. Цифрите представляват идентитетът (резултатът от функцията `id()`) на детето, което е изпратило писмото. * Желанието за подарък се дефинира като текст, който е обграден от кавички (`"`) или апострофи (`'`) и съдържа само букви от латинската азбука и/или цифри (може и интервали, разбира се). Няма да тестваме с текст, който има повече от една такава дефиниция, нито с такъв, в който тя липсва. Примерно писмо: ``` Скъпи Дядо Коледа, През по-голямата част от тази година бях много послушен! Понякога бях само леко "послушен", но т'ва не е никакъв проблем, защото всички сме хора и понякога искаме да теглим една '...' на всичко. Life is tough! Моля те донеси ми "Nimbus 2000". С много обич, 2945526885456 ``` Тук идентитетът на детето е `2945526885456`, а подаръкът, който иска, е `Nimbus 2000`. Ако съобщението е получено като обаждане, за подаръка важи същото, а подписът не е нужен, защото Дядо Коледа очаква обектът дете да влезе като отделен аргумент. * Дядо Коледа е итерируем и може да се обходи с `for`. При това повече от веднъж. Всяко обхождане на Дядо Коледа трябва да върне подаръците, които са му били поръчани за идващата Коледа. В реда, в който са били заявени. ``` santa = Santa() santa @ &lt;писмо, в което някое дете иска doll&gt; santa(&lt;дете&gt;, &lt;съобщение, което иска ball&gt;) for present in santa: print(present) # Горният код извежда: # doll # ball ``` * Дядо Коледа има публичен метод `xmas`, който не очаква аргументи. Извикването на метода раздава подаръци на децата. Правилата за раздаване на подаръци: * Дете получава подарък, като просто бъде извикано с един аргумент от тип `str`, който дефинира подаръка. ``` santa = Santa() kid = &lt;някаква инициализация на дете, която не зависи от вас&gt; santa(kid, &lt;писмо, в което детето иска drums&gt;) santa.xmas() # Очаква се детето `kid` да бъде извикано с очаквания подарък: kid('drums') ``` * Всяко дете получава това, което е поискало (с някои изключения): * Ако детето е на повече от 5 години, Дядо Коледа спира да му дава подаръци. Как знаем кое дете на колко години е? Дядо Коледа магически знае за всяко едно съществуващо дете. Когато след създаване на дете изминат 5 Коледи (5 извиквания на `xmas`), Дядо Коледа спира да се занимава с него и не му праща нищо, независимо дали е получил писмо/обаждане от детето. Дядо Коледа се интересува от едно дете само в продължение на 5 Коледи. На шестата вече го отсвирва. * Дядо Коледа раздава подаръци само на послушните деца. Деца, които са хвърлили изключение в някой от публичните си методи през изминалата година, са непослушни. Вместо подарък, те получават въглен, т.е. са извикани така: `kid('coal')`. Публичен метод дефинираме като метод, който не започва с долна черта. Няма да тестваме с публични методи, които са декорирани с `classmethod`, `staticmethod`, `property` и подобни. Само с нормални методи. * Дядо Коледа раздава подаръци дори на деца, които не са му пратили писмо и не са се обадили. Подаръците, които Дядо Коледа раздава на такива деца, съвпадат с най-желания подарък през изминалата година. Ако не е ясно кой е най-желан (има няколко желания с равен брой искания), взима се един от най-желаните на случаен принцип. ``` santa = Santa() kid1 = &lt;някаква инициализация на дете, която не зависи от вас&gt; kid2 = &lt;някаква инициализация на дете, която не зависи от вас&gt; kid3 = &lt;някаква инициализация на дете, която не зависи от вас&gt; santa @ &lt;писмо от kid1, в което се иска tablet&gt; santa @ &lt;писмо от kid2, в което се иска tablet&gt; santa.xmas() # и трите деца получават tablet-и ``` * Ако през изминалата година (т.е. след последното извикване на `xmas`) Дядо Коледа не е получил нито едно желание (с писмо или с обаждане), той не раздава нищо на никого. Явно магията на Коледа е отминала и всички са забравили за него. * Ако през изминалата година (т.е. след последното извикване на `xmas`) Дядо Коледа е получил повече от едно желание от дадено дете, използва се последното. На този етап сигурно се чудите как Дядо Коледа ще знае за всички деца, които казахме, че се инстанцират отвъд вашия контрол. ### Напишете метаклас `Kid` Всички деца, които ние ще създаваме, ще бъдат създадени от класове, които използват този метаклас. От вас зависи как ще го имплементирате. За него има само едно единствено условие: * Всеки клас, който използва `Kid` като свой метаклас, трябва да имплементира метод, с който инстанциите му да са callable. Ако се опитаме да дефинираме клас, чиито инстанции не могат да се извикват, очакваме да се възбуди `NotImplementedError` с произволен текст, който можете да използвате, за да ни покажете колко ни обичате/мразите. ``` class BulgarianKid(metaclass=Kid): pass # Това ще хвърли NotImplementedError ``` ### Малко примерен код ``` santa = Santa() class BulgarianKid(metaclass=Kid): def __call__(self, present): pass def be_naughty(self): raise RuntimeError('Няма да си изям зеленчуците!') class ChineseKid(metaclass=Kid): def __call__(self, present): pass goshko = BulgarianKid() toshko = BulgarianKid() chen = ChineseKid() santa(goshko, "'gun'") santa @ f"'gun'\n{id(toshko)}" try: goshko.be_naughty() except: pass santa.xmas() # Гошко получава coal # Тошко получава gun # Chen получава gun santa.xmas() # Никой не получава нищо, защото няма желания santa.xmas() # Никой не получава нищо, защото няма желания santa.xmas() # Никой не получава нищо, защото няма желания gencho = BulgarianKid() santa(gencho, "'whistle'") santa.xmas() # Всички деца (Гошко, Тошко, Cheng и Генчо) получават whistle kircho = BulgarianKid() santa(kircho, "'whistle'") santa.xmas() # Кирчо и Генчо получават whistle. Останалите вече са на над 5 години. ```
 1import unittest
 2
 3import solution
 4
 5
 6class TestSanity(unittest.TestCase):
 7    """Check if all of the classes are present."""
 8
 9    def test_santa(self):
10        self.assertIn('Santa', dir(solution), 'Убеди се, че класът "Santa" е наличен с точно това име.')
11        self.assertTrue(isinstance(solution.Santa, type), 'Убеди се, че "Santa" е клас.')
12
13    def test_interval(self):
14        self.assertIn('Kid', dir(solution), 'Убеди се, че класът "Kid" е наличен с точно това име.')
15        self.assertTrue(isinstance(solution.Kid, type), 'Убеди се, че "Kid" е клас.')
16
17
18if __name__ == '__main__':
19    unittest.main()
  1import unittest
  2from copy import deepcopy
  3from importlib import reload
  4
  5import solution
  6
  7
  8class TestKid(unittest.TestCase):
  9    """Test the Kid metaclass."""
 10
 11    def test_class_from_kid(self):
 12        """Test creating new class from Kid."""
 13        cls = solution.Kid('name', (), {'__call__': print})
 14        self.assertIsInstance(cls, solution.Kid)
 15
 16    def test_class_from_kid_without_call_dunder(self):
 17        """Test creating new class from Kid."""
 18        with self.assertRaises(NotImplementedError):
 19            solution.Kid('name', (), {})
 20
 21
 22class TestSanta(unittest.TestCase):
 23    """Test the Santa metaclass."""
 24
 25    KID_DICT = {
 26        '__init__': lambda self: setattr(self, 'SECRET_PRESENT', None),
 27        '__call__': lambda self, present: setattr(self, 'SECRET_PRESENT', present),
 28        'public_with_error': lambda self: 0 / 0,
 29        'public_without_error': lambda self: 42,
 30        '_private_with_error': lambda self: not_existing,
 31    }
 32
 33    def setUp(self):
 34        """Create isntances to be used by test cases."""
 35        self.KidClass1 = solution.Kid('KidClass1', (), deepcopy(self.KID_DICT))
 36        self.KidClass2 = solution.Kid('KidClass2', (), deepcopy(self.KID_DICT))
 37        self.santa = solution.Santa()
 38
 39    def tearDown(self):
 40        """Reload the classes."""
 41        del self.KidClass1
 42        del self.KidClass2
 43        del self.santa
 44        reload(solution)
 45
 46    def test_singleton(self):
 47        """Ensure Santa is a singleton."""
 48        self.assertIs(solution.Santa(), solution.Santa())
 49
 50    def test_call(self):
 51        """Test sending message via calling."""
 52        kid1 = self.KidClass1()
 53        kid2 = self.KidClass2()
 54        self.santa(kid1, "'toy1'")
 55        self.santa(kid2, "'toy2'")
 56        self.assertEqual(list(self.santa), ['toy1', 'toy2'])
 57
 58    def test_mail(self):
 59        """Test sending message via email."""
 60        kid1 = self.KidClass1()
 61        kid2 = self.KidClass2()
 62        self.santa @ f"'toy1'\n{id(kid1)}"
 63        self.santa @ f"'toy2'\n{id(kid2)}"
 64        self.assertEqual(list(self.santa), ['toy1', 'toy2'])
 65
 66    def test_call_and_mail_same_kid(self):
 67        """Test that calls and mails work for the same kid."""
 68        kid1 = self.KidClass1()
 69        self.santa(kid1, "'toy1'")
 70        self.santa @ f"'toy1'\n{id(kid1)}"
 71        self.assertEqual(list(self.santa), ['toy1'])
 72
 73    def test_present_matching(self):
 74        """Test matching signature in the letter."""
 75        kid1 = self.KidClass1()
 76        kid2 = self.KidClass1()
 77        # Misleading toy definiton
 78        self.santa @ f"""
 79            Това е примерен текст, в който има текст в "кавички" и
 80            'апострофи', но на кирилица.
 81
 82            'toy4' а това е текст след дефиницията на играчка с кавичка и апостроф " '
 83
 84            {id(kid1)}
 85        """
 86        # Complex toy definiton after the signature
 87        self.santa @ f"""
 88            {id(kid2)}
 89
 90            "abcdefgHIJKLMNopQRstUVwxYZ 1 2 3 4567890   "
 91        """
 92        self.assertEqual(list(self.santa), ['toy4', 'abcdefgHIJKLMNopQRstUVwxYZ 1 2 3 4567890   '])
 93
 94    def test_signature_matching(self):
 95        """Test matching present in the letter / call."""
 96        kid1 = self.KidClass1()
 97        kid2 = self.KidClass1()
 98        kid3 = self.KidClass1()
 99        # Whitespaces around signature
100        self.santa @ f"""
101            'toy1'
102
103            {id(kid1)}
104        """
105        # Misleading signature 1
106        self.santa @ f"""
107            'toy2'
108            invalid signature row 12345678
109            {id(kid2)}
110        """
111        # Misleading signature 2
112        self.santa @ f"""
113            'toy3'
114            " 12345678
115            {id(kid3)}
116        """
117        self.santa.xmas()
118        self.assertEqual(kid1.SECRET_PRESENT, 'toy1')
119        self.assertEqual(kid2.SECRET_PRESENT, 'toy2')
120        self.assertEqual(kid3.SECRET_PRESENT, 'toy3')
121
122    def test_iterable(self):
123        """Ensure Santa can be iterated multiple times including overwriting presents."""
124        kid1 = self.KidClass1()
125        kid2 = self.KidClass2()
126        self.santa(kid1, '"something"')
127        self.santa(kid2, '"something not used"')
128        self.santa(kid2, '"something else"')
129        self.assertEqual(list(self.santa), ['something', 'something else'])
130        self.assertEqual(list(self.santa), ['something', 'something else'])
131
132    def test_xmass(self):
133        """Test a simple Christmas case."""
134        kid1 = self.KidClass1()
135        kid2 = self.KidClass1()
136        kid3 = self.KidClass2()
137        self.santa(kid1, '"toy1"')
138        self.santa(kid2, '"toy2"')
139        self.santa @ f"'toy3'\n{id(kid3)}"
140        self.santa.xmas()
141        self.assertEqual(kid1.SECRET_PRESENT, 'toy1')
142        self.assertEqual(kid2.SECRET_PRESENT, 'toy2')
143        self.assertEqual(kid3.SECRET_PRESENT, 'toy3')
144
145    def test_xmass_no_wishes(self):
146        """Test a Christmas with no wishes."""
147        kid1 = self.KidClass1()
148        kid2 = self.KidClass1()
149        kid3 = self.KidClass2()
150        self.santa.xmas()
151        self.assertEqual(kid1.SECRET_PRESENT, None)
152        self.assertEqual(kid2.SECRET_PRESENT, None)
153        self.assertEqual(kid3.SECRET_PRESENT, None)
154
155    def test_xmass_no_wishes_but_naughty_kids(self):
156        """Test a Christmas with no wishes, but naughty kids present."""
157        kid1 = self.KidClass1()
158        try:
159            kid1.public_with_error() # Don't care if it's decorated properly
160        except:
161            pass
162        self.santa.xmas()
163        self.assertEqual(kid1.SECRET_PRESENT, None)
164
165    def test_xmass_kid_without_a_wish(self):
166        """Test a Christmas with a kids that hasn't sent a wish."""
167        kid1 = self.KidClass1()
168        kid2 = self.KidClass1()
169        kid3 = self.KidClass2()
170        kid4 = self.KidClass2()
171        self.santa(kid1, '"toy1"')
172        self.santa(kid2, '"toy1"')
173        self.santa @ f"'toy2'\n{id(kid3)}"
174        self.santa.xmas()
175        self.assertEqual(kid1.SECRET_PRESENT, 'toy1')
176        self.assertEqual(kid2.SECRET_PRESENT, 'toy1')
177        self.assertEqual(kid3.SECRET_PRESENT, 'toy2')
178        self.assertEqual(kid4.SECRET_PRESENT, 'toy1')
179
180    def test_xmass_kid_with_multiple_wishes(self):
181        """Test a Christmas with a kid who sends multiple wishes."""
182        kid1 = self.KidClass1()
183        self.santa(kid1, '"toy1"')
184        self.santa(kid1, '"toy2"')
185        self.santa(kid1, '"toy3"')
186        self.santa.xmas()
187        self.assertEqual(kid1.SECRET_PRESENT, 'toy3')
188
189    def test_xmass_years_under_5(self):
190        """Test with passing years with a kid under 5 years old."""
191        kid1 = self.KidClass1()
192        self.santa(kid1, '"toy1"')
193        self.santa.xmas()  # Christmas 1
194        self.assertEqual(kid1.SECRET_PRESENT, 'toy1')
195        kid1.SECRET_PRESENT = None
196        self.santa.xmas()  # Christmas 2
197        self.santa.xmas()  # Christmas 3
198        self.santa.xmas()  # Christmas 4
199        self.santa(kid1, '"toy2"')
200        self.santa.xmas()  # Christmas 5
201        self.assertEqual(kid1.SECRET_PRESENT, 'toy2')
202
203    def test_xmass_years_5_and_over(self):
204        """Test with passing years with kid aged 5 and over."""
205        kid1 = self.KidClass1()
206        self.santa.xmas()  # Christmas 1
207        self.santa.xmas()  # Christmas 2
208        self.santa.xmas()  # Christmas 3
209        self.santa.xmas()  # Christmas 4
210        self.santa.xmas()  # Christmas 5
211        self.santa(kid1, '"toy"')
212        self.santa.xmas()  # Christmas 6
213        self.assertEqual(kid1.SECRET_PRESENT, None)
214
215    def test_xmass_naughty(self):
216        """Test a Christmas with naughty kids."""
217        kid1 = self.KidClass1()
218        kid2 = self.KidClass1()
219        kid3 = self.KidClass2()
220        self.santa(kid1, "'sirenie'")
221        self.santa(kid2, "'sirenie'")
222        with self.assertRaises(ZeroDivisionError):
223            kid1.public_with_error()
224        with self.assertRaises(ZeroDivisionError):
225            kid2.public_with_error()
226        self.santa.xmas()
227        self.assertEqual(kid1.SECRET_PRESENT, 'coal')
228        self.assertEqual(kid2.SECRET_PRESENT, 'coal')
229        self.assertEqual(kid3.SECRET_PRESENT, 'sirenie')
230
231    def test_xmass_private_with_error(self):
232        """Test a Christmas with not-so-naughty kids."""
233        kid1 = self.KidClass1()
234        self.santa(kid1, "'sirenie'")
235        try:
236            kid1._private_with_error()
237        except:
238            pass
239        self.santa.xmas()
240        self.assertEqual(kid1.SECRET_PRESENT, 'sirenie')
241
242    def test_xmass_public_with_no_error(self):
243        """Test a Christmas with not-so-naughty kids."""
244        kid1 = self.KidClass1()
245        self.santa(kid1, "'sirenie'")
246        self.assertEqual(kid1.public_without_error(), 42)
247        self.santa.xmas()
248        self.assertEqual(kid1.SECRET_PRESENT, 'sirenie')
249
250    def test_santa_gift_order(self):
251        """Test ordering of the Santa iterator."""
252        kid1 = self.KidClass1()
253        kid2 = self.KidClass1()
254        kid3 = self.KidClass2()
255        self.santa(kid2, '"toy2"')
256        self.santa @ f"'toy3'\n{id(kid3)}"
257        self.santa(kid1, '"toy1"')
258        self.santa(kid2, '"toy2v2"')
259        # Kid order must remain kid2, kid3, kid1, as the order of initial requests
260        self.assertEqual(list(self.santa), ["toy2v2", "toy3", "toy1"])
261
262
263if __name__ == '__main__':
264    unittest.main()
Дискусия
Даниел Стефанов
13.12.2024 11:50

Може ли едно дете да поиска два подаръка в една година?
Нишка
Георги Кунчев
13.12.2024 12:52

"Ако през изминалата година (т.е. след последното извикване на xmas) Дядо Коледа е получил повече от едно желание от дадено дете, използва се последното."
Павел Петков
13.12.2024 09:50

```Скъпи Дядо Коледа, През по-голямата част от тази година бях много послушен! Понякога бях само леко "послушен", но т'ва не е никакъв проблем, защото всички сме хора и понякога искаме да теглим една '...' на всичко. Life is tough! Моля те донеси ми "Nimbus 2000". С много обич, 2945526885456 ``` В този пример има "" или '' на три места, които са "послушен", '...' и "Nimbus 2000", как да разберем кое е подаръка?
Нишка
Виктор Бечев
13.12.2024 09:53

Желанието за подарък се дефинира като текст, който е обграден от кавички (") или апострофи (') и съдържа **само букви от латинската азбука и/или цифри** (може и интервали, разбира се).