Домашни > Великденско домашно > Решения > Решението на Никола Нейчев

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

6 точки общо

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

  1#!/usr/bin/python3
  2import unittest
  3
  4class Egg:
  5
  6    def __init__(self):
  7        self._colors = []
  8        self._painted_so_far = 0
  9        self._broken_up = False
 10        self._broken_down = False
 11        self._tournament = None
 12
 13    @property
 14    def colors(self):
 15        return tuple(self._colors)
 16
 17    @property
 18    def painted_so_far(self):
 19        return self._painted_so_far
 20
 21    @property 
 22    def broken_up(self):
 23        return self._broken_up
 24
 25    @property 
 26    def broken_down(self):
 27        return self._broken_down
 28
 29    @property
 30    def tournament(self):
 31        return self._tournament
 32
 33    @colors.setter
 34    def colors(self, value):
 35        print("You can't change colors like this!")
 36        raise ValueError("Wrong command!")
 37
 38    @painted_so_far.setter
 39    def painted_so_far(self, value):
 40        print("You can't change colors like this!")
 41        raise ValueError("Wrong command!")
 42
 43    @broken_up.setter
 44    def broken_up(self, value):
 45        print("You can't change broken status like this!")
 46        raise ValueError("Wrong command!")
 47
 48    @broken_down.setter
 49    def broken_down(self, value):
 50        print("You can't change broken status like this!")
 51        raise ValueError("Wrong command!")
 52
 53    @tournament.setter
 54    def tournament(self, value):
 55        print("You can't change tournament like this!")
 56        raise ValueError("Wrong command!")
 57
 58    def paint(self, *color_data):
 59        """
 60        Records the coloring process of the egg based on the provided color pairs.
 61
 62        Args:
 63            *color_pairs: Arbitrary number of tuples in the format (hex_color, percentage),
 64                where hex_color is a string and percentage is a positive float.
 65
 66        Raises:
 67            ValueError: If the total percentage after the update exceeds 100%. 
 68                No changes are applied to the egg's state if an error is raised.
 69
 70        Notes:
 71            - The method can be called multiple times until the cumulative percentage 
 72              reaches exactly 100%.
 73            - Hex colors are case-insensitive (e.g., "aabcd0" is treated the same as "AABCD0").
 74            - Percentages are always real numbers greater than 0.
 75            - Pairs are processed in the order they are provided.
 76            - Invalid inputs (colors or percentages <= 0) are not expected and not handled.
 77        """ 
 78
 79        colors_to_append = []
 80        percentages_to_append = 0
 81        for hex_color, percentage in color_data:
 82            colors_to_append.append((hex_color.upper(), percentage))
 83            percentages_to_append += percentage
 84
 85        if percentages_to_append + self._painted_so_far > 100:
 86            raise ValueError("Percentages above 100!")
 87
 88        self._colors.extend(colors_to_append)
 89        self._painted_so_far += percentages_to_append
 90    
 91    @property
 92    def strength_up(self):
 93        total_strength = 0
 94        current_pos = 0
 95        
 96        for color, percentage in self.colors:
 97            color_start = current_pos
 98            color_end = current_pos + percentage
 99
100            overlap_start = max(color_start, 0)
101            overlap_end = min(color_end, 50)
102
103            if overlap_end > overlap_start:
104                active_part = overlap_end - overlap_start
105                
106                R = int(color[0:2], 16)
107                G = int(color[2:4], 16)
108                B = int(color[4:6], 16)
109
110                total_strength += (R + G + B) * ( active_part / 50 )
111            
112            current_pos += percentage
113
114        return total_strength
115    
116
117    @property
118    def strength_down(self):
119        total_strength = 0
120        current_pos = 0
121        
122        for hex_color, percentage in self._colors:
123            color_start = current_pos
124            color_end = current_pos + percentage
125            
126            overlap_start = max(color_start, 50)
127            overlap_end = min(color_end, 100)
128            
129            if overlap_end > overlap_start:
130                active_part = overlap_end - overlap_start
131                
132                R = int(hex_color[0:2], 16)
133                G = int(hex_color[2:4], 16)
134                B = int(hex_color[4:6], 16)
135                
136                total_strength += (R + G + B) * (active_part / 50)
137            
138            current_pos += percentage
139            
140        return total_strength
141
142    def __mul__(self, other): # *
143        """Simulates a collision between eggs from the top side."""
144        if self.broken_up:
145            raise TypeError("The first egg is broken!")
146        
147        if other.broken_up:
148            raise TypeError("The second egg is broken!")
149
150        if self._tournament != None and other._tournament != None and self._tournament is other._tournament:
151            validTournament = True
152        else:
153            validTournament = False
154
155        if len(self.colors) == 0:
156            self._broken_up = True
157
158            if validTournament:
159                self._tournament.battles.append((self, other, other, "top"))
160
161            return other
162        elif len(other.colors) == 0:
163            other._broken_up = True
164
165            if validTournament:
166                self._tournament.battles.append((self, other, self, "top"))
167
168            return self
169
170        if self.strength_up < other.strength_up:
171            self._broken_up = True
172
173            if validTournament:
174                self._tournament.battles.append((self, other, other, "top"))
175            
176            return other
177        else:
178            other._broken_up = True
179
180            if validTournament:
181                self._tournament.battles.append((self, other, self, "top"))
182
183            return self
184
185
186    def __matmul__(self, other): # @
187        """Simulates a collision between eggs from the bottom side."""
188
189        if self.broken_down:
190            raise TypeError("The first egg is broken!")
191        
192        if other.broken_down:
193            raise TypeError("The second egg is broken!")
194
195        if self._tournament != None and other._tournament != None and self._tournament is other._tournament:
196            validTournament = True
197        else:
198            validTournament = False
199
200        if self.strength_down == 0 and self.painted_so_far <= 50:
201            self._broken_down = True
202
203            if validTournament:
204                self._tournament.battles.append((self, other, other, "bottom"))
205
206            return other
207        elif other.strength_down == 0 and other.painted_so_far <= 50:
208            other._broken_down = True
209
210            if validTournament:
211                self._tournament.battles.append((self, other, self, "bottom"))
212
213            return self
214
215        if self.strength_down < other.strength_down:
216            self._broken_down = True
217
218            if validTournament:
219                self._tournament.battles.append((self, other, other, "bottom"))
220
221            return other
222        else:
223            other._broken_down = True
224
225            if validTournament:
226                self._tournament.battles.append((self, other, self, "bottom"))
227
228            return self
229    
230    def __hash__(self):
231        return id(self)
232
233    def __eq__(self, other):
234        return self is other
235
236
237def valid_name(name):
238    for symbol in name:
239        if symbol.isupper():
240            return False
241
242    return True
243
244
245class EggTournament:
246    """
247    Manages and orchestrates collisions and duels between registered eggs.
248
249    This class acts as a controller that monitors interactions, maintains 
250    a detailed history of all collisions, and tracks egg rankings. It provides 
251    functionality to query collision data and rankings based on specific criteria.
252
253    Key Responsibilities:
254        - Monitoring duels between registered egg instances.
255        - Storing a comprehensive log of all collision events.
256        - Maintaining up-to-date leaderboards and statistics.
257        - Facilitating data retrieval through various search filters.
258    """
259
260    def __init__(self):
261        self._eggs = {}
262        self._nicknames = {}
263        self.battles = []
264
265    def register(self, egg, nickname):
266        
267        if egg.tournament != None:
268            raise ValueError("An egg cannot be registered in multiple tournaments")
269
270        if not valid_name(nickname):
271            raise ValueError("Invalid registration name")
272
273        if nickname in self._nicknames:
274            raise ValueError(f"Egg with name {nickname} has already been registered")
275
276        self._eggs[egg] = nickname
277        self._nicknames[nickname] = egg
278        egg._tournament = self
279
280    def __getitem__(self, key):
281        if isinstance(key, tuple):
282            egg1, egg2, battle_way = key
283        elif isinstance(key, slice):
284            egg1, egg2, battle_way = key.start, key.stop, key.step
285        
286        for curr_egg1, curr_egg2, curr_result, curr_battle_way in self.battles:
287            if {curr_egg1, curr_egg2} == {egg1, egg2} and curr_battle_way == battle_way:
288                return curr_result
289                
290        raise KeyError("Battle not found")
291
292    def __ranks(self):
293        win_counts = {egg: 0 for egg in self._eggs}
294
295        for match in self.battles:
296            winner = match[2]
297            win_counts[winner] += 1
298
299        list_to_sort = []
300        for egg, wins in win_counts.items():
301            nickname = self._eggs[egg]
302            list_to_sort.append((wins, nickname, egg))
303
304        sorted_list = sorted(list_to_sort, key = lambda x: (-x[0],x[1]))
305
306        rank = 0
307        last_wins = -1
308        final_ranks = {}
309
310        for wins, nick, egg in sorted_list:
311            if wins != last_wins:
312                rank += 1
313            final_ranks[egg] = rank
314            last_wins = wins
315        
316        return final_ranks
317
318    def ranking(self):
319        ranks_dict = self.__ranks()
320        res = []
321        for egg, r in ranks_dict.items():
322            res.append((r, self._eggs[egg]))
323        return sorted(res, key=lambda x: (x[0], x[1]))
324
325    def __rmatmul__(self, rank):
326
327        final_ranks = self.__ranks() 
328
329        matched_eggs = [eg for eg, r in final_ranks.items() if r == rank]
330
331        if not matched_eggs:
332            raise IndexError(f"No eggs at rank {rank}")
333
334        if len(matched_eggs) == 1:
335            return matched_eggs[0]
336        return set(matched_eggs)
337
338    def __getattr__(self, name):
339        if name in self._nicknames:
340            egg = self._nicknames[name]
341
342            victories = sum(1 for match in self.battles if match[2] == egg)
343
344            all_ranks = self.__ranks()
345            position = all_ranks[egg]
346
347            return {"position": position, "victories": victories}
348
349        raise AttributeError("Apologies, there is no such egg registered")
350
351    def __contains__(self, egg):
352        return egg in self._eggs
353
354
355class TestEggsMethods(unittest.TestCase):
356
357    def test_init(self):
358        egg = Egg()
359        self.assertEqual(egg.painted_so_far, 0)
360        self.assertEqual(len(egg.colors), 0)
361        self.assertFalse(egg.broken_up)
362        self.assertFalse(egg.broken_down)
363        self.assertIsNone(egg.tournament)
364
365    def test_setter(self):
366        egg = Egg()
367        with self.assertRaises(ValueError):
368            egg.colors = [("FFFFFF", 100)]
369        with self.assertRaises(ValueError):
370            egg.painted_so_far = 30
371        with self.assertRaises(ValueError):
372            egg.broken_up = True
373        with self.assertRaises(ValueError):
374            egg.broken_down = True
375        with self.assertRaises(ValueError):
376            egg.tournament = 'Искам бонус точки за unittest-овете'
377
378    def test_paint(self):
379        egg = Egg()
380        egg.paint(("FFFFFF", 99))
381        self.assertIn(("FFFFFF", 99), egg.colors)
382        egg.paint(("FF0000", 0.3),("00FF00", 0.7))
383        self.assertIn(("FF0000", 0.3), egg.colors)
384        self.assertIn(("00FF00", 0.7), egg.colors)
385
386        with self.assertRaises(ValueError):
387            egg.paint(("FF0000", 20))
388   
389    def test_strength_up(self):
390        egg1 = Egg()
391        egg1.paint(("AA2C00", 50.0))
392        egg1.paint(("FFFF00", 50.0))
393        self.assertEqual(egg1.strength_up, 170 + 44)
394        
395        egg2 = Egg()
396        egg2.paint(("0000FF", 25.0), ("DD0000", 25.0))
397        self.assertEqual(egg2.strength_up, 255 * 0.5 + 221 * 0.5)
398
399    def test_strength_down(self):
400        egg1 = Egg()
401        egg1.paint(("AA2C00", 50.0))
402        egg1.paint(("FFFF00", 50.0))
403        self.assertEqual(egg1.strength_down, 255 + 255)
404
405        egg2 = Egg()
406        egg2.paint(("0000FF", 25.0), ("DD0000", 25.0), ("FFFE00", 50.0))
407        self.assertEqual(egg2.strength_down, 255 + 254)
408
409    def test_mul(self):
410        egg1 = Egg()
411        egg2 = Egg()
412
413        egg1.paint(("AA2C00", 50.0))
414        egg1.paint(("FFFF00", 50.0))
415        egg2.paint(("0000FF", 25.0), ("DD0000", 25.0), ("FFFE00", 50.0))
416        
417        self.assertIs(egg1 * egg2, egg2)
418
419        with self.assertRaises(TypeError):
420            egg1 * egg2
421
422        egg1 = Egg()
423        egg2 = Egg()
424        egg2.paint(("000000", 50.0))
425        self.assertIs(egg1 * egg2, egg2)
426
427    def test_matmul(self):
428        egg1 = Egg()
429        egg2 = Egg()
430
431        egg1.paint(("AA2C00", 50.0))
432        egg1.paint(("FFFF00", 50.0))
433        egg2.paint(("0000FF", 25.0), ("DD0000", 25.0), ("FFFE00", 50.0))
434        
435        self.assertIs(egg1 @ egg2, egg1)
436
437        with self.assertRaises(TypeError):
438            egg1 @ egg2
439
440        egg1 = Egg()
441        egg2 = Egg()
442        egg2.paint(("000000", 100.0))
443        self.assertIs(egg1 @ egg2, egg2)
444
445
446class TestEggTournament(unittest.TestCase):
447
448    def test_init(self):
449        tournament = EggTournament()
450        self.assertEqual(len(tournament._eggs), 0)
451        self.assertEqual(len(tournament._nicknames), 0)
452        self.assertEqual(len(tournament.battles), 0)
453
454    def test_register(self):
455        egg = Egg()
456        tournament = EggTournament()
457
458        tournament.register(egg, "biqch")
459        self.assertIn(egg, tournament._eggs) 
460        self.assertIn("biqch", tournament._nicknames)
461
462        tournament2 = EggTournament()
463
464        with self.assertRaises(ValueError):
465            tournament2.register(egg, "biqch")
466    
467    def test_battles_with_valid_data(self):
468        egg1 = Egg()
469        egg2 = Egg()
470
471        tournament = EggTournament()
472        tournament.register(egg1, "biqch")
473        tournament.register(egg2, "nebiqch")
474
475        egg1.paint(("FFFFFF", 100.0 ))
476        egg2.paint(("F0F0F0", 100.0))
477
478        res_egg = egg1 * egg2
479
480        self.assertIs(res_egg, egg1)
481        self.assertIn((egg1, egg2, res_egg, "top"), tournament.battles)
482
483    def test_battles_with_wrong_tournaments_for_second_egg(self):
484        egg1 = Egg()
485        egg2 = Egg()
486
487        tournament = EggTournament()
488        egg1.paint(("FFFFFF", 100.0 ))
489        egg2.paint(("F0F0F0", 100.0))
490        tournament.register(egg1, "biqch")
491
492        res_egg = egg1 * egg2
493
494        self.assertIs(res_egg, egg1)
495        self.assertNotIn((egg1, egg2, res_egg, "top"), tournament.battles)
496
497    def test_battles_with_wrong_tournaments_for_first_egg(self):
498        egg1 = Egg()
499        egg2 = Egg()
500
501        tournament = EggTournament()
502        egg1.paint(("FFFFFF", 100.0 ))
503        egg2.paint(("F0F0F0", 100.0))
504        tournament.register(egg2, "biqch")
505
506        res_egg = egg1 * egg2
507
508        self.assertIs(res_egg, egg1)
509        self.assertNotIn((egg1, egg2, res_egg, "top"), tournament.battles)
510
511    def test_battles_with_wrong_tournaments_for_first_and_second_egg(self):
512        egg1 = Egg()
513        egg2 = Egg()
514
515        tournament = EggTournament()
516        egg1.paint(("FFFFFF", 100.0 ))
517        egg2.paint(("F0F0F0", 100.0))
518
519        res_egg = egg1 * egg2
520
521        self.assertIs(res_egg, egg1)
522        self.assertNotIn((egg1, egg2, res_egg, "top"), tournament.battles)
523
524    def test_register_with_wrong_nickname(self):
525        egg = Egg()
526        nickanme = "TheBestNicknameInTheWorld"
527        tournament = EggTournament()
528
529        with self.assertRaises(ValueError):
530            tournament.register(egg, nickanme)
531
532    def test_register_with_occupied_nickname(self):
533        egg1 = Egg()
534        nickname = "not_the_best_nickname_in_the_world"
535        tournament = EggTournament()
536        tournament.register(egg1,nickname)
537
538        egg2 = Egg()
539        with self.assertRaises(ValueError):
540            tournament.register(egg2, nickname)
541
542    def test_getitem_with_valid_data(self):
543        egg1 = Egg()
544        egg2 = Egg()
545
546        egg1.paint(("FFFFFF", 100.0))
547        egg2.paint(("F0F0F0", 100.0))
548
549        tournament = EggTournament()
550
551        tournament.register(egg1, "winner")
552        tournament.register(egg2, "loser")
553
554        egg1 * egg2
555
556        result = tournament[(egg1, egg2, "top")]
557        self.assertIs(egg1, result)
558
559        result = tournament[egg1:egg2:"top"]
560        self.assertIs(egg1, result)
561
562    def test_getitem_with_wrong_data(self):
563        tournament = EggTournament()
564        egg1 = Egg()
565        egg2 = Egg()
566        with self.assertRaises(KeyError):
567            result = tournament[egg1:egg2:"bottom"]
568    
569    def test_ranks(self):
570        tournament = EggTournament()
571
572        egg1 = Egg()
573        egg2 = Egg()
574        egg3 = Egg()
575        egg4 = Egg()
576
577        tournament.register(egg1, "alpha")
578        tournament.register(egg2, "beta")
579        tournament.register(egg3, "gamma")
580        tournament.register(egg4, "delta")
581
582        tournament.battles.append((egg1, egg2, egg1, "top"))
583        tournament.battles.append((egg1, egg3, egg1, "top"))
584        tournament.battles.append((egg2, egg3, egg2, "top"))
585        tournament.battles.append((egg4, egg3, egg4, "top"))
586
587        expected_ranking = [(1, "alpha"), (2, "beta"), (2, "delta"), (3, "gamma")]
588        self.assertEqual(tournament.ranking(), expected_ranking)
589
590        self.assertEqual(1 @ tournament, egg1)
591
592        rank2_result = 2 @ tournament
593        self.assertIsInstance(rank2_result, set)
594        self.assertEqual(rank2_result, {egg2, egg4})
595
596        with self.assertRaises(IndexError):
597            _ = 10 @ tournament
598
599    def test_contains(self):
600        tournament = EggTournament()
601        egg = Egg()
602        
603        self.assertFalse(egg in tournament)
604        
605        tournament.register(egg, "tester")
606        self.assertTrue(egg in tournament)
607        self.assertIn(egg, tournament)
608
609    def test_attribute_access_success(self):
610        tournament = EggTournament()
611        egg = Egg()
612        tournament.register(egg, "the_monster")
613        
614        egg_other = Egg()
615        tournament.battles.append((egg, egg_other, egg, "top"))
616        tournament.battles.append((egg, egg_other, egg, "bottom"))
617        
618        expected = {"position": 1, "victories": 2}
619        self.assertEqual(tournament.the_monster, expected)
620
621    def test_attribute_access_failure(self):
622        tournament = EggTournament()
623        
624        with self.assertRaises(AttributeError) as cm:
625            _ = tournament.missing_egg
626            
627        self.assertEqual(str(cm.exception), "Apologies, there is no such egg registered")
628
629    def test_attribute_access_real_attribute(self):
630        tournament = EggTournament()
631        self.assertEqual(tournament.battles, [])
632
633
634class TestValidName(unittest.TestCase):
635    
636    def test_valid_name(self):
637        first_nickname = 'valid_word'
638        second_nickname = 'InValid_word'
639        third_nickname = 'invalid_Word'
640        fourth_nickname = 'valid_word_1'
641        fifth_nickname = 'InValid_Word'
642        
643        self.assertTrue(valid_name(first_nickname))
644        self.assertFalse(valid_name(second_nickname))
645        self.assertFalse(valid_name(third_nickname))
646        self.assertTrue(valid_name(fourth_nickname))
647        self.assertFalse(valid_name(fifth_nickname))
648
649if __name__ == "__main__":
650    unittest.main()

...........................................F..........
======================================================================
FAIL: test_register_invalid_name_raises_value_error (test.TestEggTournament.test_register_invalid_name_raises_value_error)
Registering an egg with an invalid name should raise a ValueError.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 312, in test_register_invalid_name_raises_value_error
with self.assertRaises(ValueError) as context:
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
AssertionError: ValueError not raised

----------------------------------------------------------------------
Ran 54 tests in 0.002s

FAILED (failures=1)

Дискусия
Виктор Бечев
15.04.2026 14:14

И моля те не качвай повече тестове така, че объркват бройката на test suite-а. 😅 Ако си написал тестове и искаш обратна връзка - качи ги в gist и ни сподели линка.
Виктор Бечев
15.04.2026 13:54

За някои от тестовете на яйцата - хубаво е да имаш edge case-ове. Например - какво става когато боядисаме яйце на 40%, после 20%, после още 40%. Само един пример, има много такива. Но разбира се, kudos за това, че изобщо си написал тестове, не очакваме от вас да пишете десетки тестове, както ние правим. Споменавам го като идея, когато започнеш да пишеш повечко тестове за неща, за които наистина е важно това покритие.
История
Това решение има само една версия.