Предизвикателства > Runewords Calculator


Runewords Calculator
Краен срок: 22.04.2026 18:00
Точки: 1

Вдъхновени от уъркшопа ни, искаме да ви споделим едно простичко предизвикателство в духа на едно от нещата, които шеговито подхвърлихме в началните слайдове на лекцията от миналата сряда. ### RunewordsCalculator Създайте клас `RunewordsCalculator`, който представлява итератор. При създаване класът получава колекция от runewords — речник, в който ключът е името на runeword, а стойността е рецепта — последователност от руни. Освен това, калкулаторът ни трябва да има метод `add_runes(runes)`, който добавя нови руни към вече наличните. ``` calculator = RunewordsCalculator({ "Enigma": ("Ber", "Ith", "Eld") }) calculator.add_runes(["Ber", "Ith", "Eld"]) ``` #### Поведение при итерация Дотук класът ни не прави нищо интересно. **НО!** При итериране през обекта: - Се връща първият по ред runeword измежду тези, които все още не са били върнати и могат да бъдат сглобени с руните, добавени досега. - Runewords се връщат в реда, в който са подадени първоначално - по приоритет, а не като фиксирана последователност. - Всеки runeword може да бъде върнат само веднъж. Един runeword може да бъде сглобен, ако всички руни от рецептата му се срещат сред добавените досега руни: - В същия ред. - Но не задължително една до друга. Ако в момента няма runeword, който може да бъде върнат, `next()` трябва да върне `None`. Ако всички runewords вече са били върнати, итерацията трябва да приключи със `StopIteration`. ##### Пример: ``` calculator = RunewordsCalculator({ "Enigma": ("Ber", "Ith", "Eld") }) print(next(iter(calculator))) # None calculator.add_runes(["Ber", "Ith", "Eld"]) print(next(iter(calculator))) # Enigma print(next(iter(calculator))) # Хвърля StopIteration ``` #### Уговорки - Добавените руни се натрупват. - Ако в рецепта има повтарящи се руни, те трябва да присъстват достатъчно пъти и в правилния ред.
 1import unittest
 2
 3import solution
 4
 5
 6class TestSanity(unittest.TestCase):
 7    """Check if all data is present."""
 8
 9    def test_data(self):
10        names = [
11            "RunewordsCalculator",
12        ]
13        unimported = [name for name in names if name not in dir(solution)]
14        self.assertEqual(unimported, [], "\n\nЕлементите по-горе липсват (проверете си имената)!")
15
16
17if __name__ == "__main__":
18    unittest.main()
 1import unittest
 2
 3from solution import RunewordsCalculator
 4
 5
 6class TestRunewordsCalculator(unittest.TestCase):
 7    def test_empty_calculator_raises_stop_iteration(self):
 8        calculator = RunewordsCalculator({})
 9        with self.assertRaises(StopIteration):
10            next(iter(calculator))
11
12    def test_returns_none_when_not_enough_runes_yet(self):
13        calculator = RunewordsCalculator({"Enigma": ("Ber", "Ith", "Eld")})
14        calculator.add_runes(["Ber", "Ith"])
15        self.assertIsNone(next(iter(calculator)))
16
17    def test_order_matters_but_adjacency_does_not(self):
18        with self.subTest("correct_order_with_gaps"):
19            calculator = RunewordsCalculator({"Enigma": ("Ber", "Ith", "Eld")})
20            calculator.add_runes(["Ber", "Tal", "Ith", "Ort", "Eld"])
21            self.assertEqual(next(iter(calculator)), "Enigma")
22        with self.subTest("wrong_order"):
23            calculator = RunewordsCalculator({"Enigma": ("Ber", "Ith", "Eld")})
24            calculator.add_runes(["Ith", "Ber", "Eld"])
25            self.assertIsNone(next(iter(calculator)))
26
27    def test_none_does_not_end_iteration_and_returned_runeword_does_not_repeat(self):
28        calculator = RunewordsCalculator({"Enigma": ("Ber", "Ith", "Eld")})
29        self.assertIsNone(next(iter(calculator)))
30        calculator.add_runes(["Ber", "Ith", "Eld"])
31        self.assertEqual(next(iter(calculator)), "Enigma")
32        with self.assertRaises(StopIteration):
33            next(iter(calculator))
34        calculator.add_runes(["Ber", "Ith", "Eld"])
35        with self.assertRaises(StopIteration):
36            next(iter(calculator))
37
38    def test_duplicate_runes_in_recipe_matter(self):
39        calculator = RunewordsCalculator({"Dupes": ("Ber", "Ber", "Eld")})
40        calculator.add_runes(["Ber", "Eld"])
41        self.assertIsNone(next(iter(calculator)))
42        calculator.add_runes(["Ber", "Eld"])
43        self.assertEqual(next(iter(calculator)), "Dupes")
44
45    def test_skips_unbuildable_runewords_and_returns_next_buildable_one(self):
46        calculator = RunewordsCalculator({"First": ("Ber", "Ith", "Eld"), "Second": ("Tal",)})
47        calculator.add_runes(["Tal"])
48        self.assertEqual(next(iter(calculator)), "Second")
49
50    def test_returns_runewords_in_runeword_order(self):
51        calculator = RunewordsCalculator({"Second": ("Ber", "Ith"), "First": ("Ber",)})
52        iterator = iter(calculator)
53        calculator.add_runes(["Ber", "Ith"])
54        self.assertEqual(next(iterator), "Second")
55        self.assertEqual(next(iterator), "First")
56        with self.assertRaises(StopIteration):
57            next(iterator)
58
59
60if __name__ == "__main__":
61    unittest.main()
Дискусия
Гергана Панделиева
22.04.2026 11:08

Ако имаш calculator = RunewordsCalculator({ "A": ("Ber", "Ith"), "B": ("Ber", "Ith", "Eld") }) calculator.add_runes(["Ber", "Ith", "Eld"]) Това означава ли, че вече и А, и В могат да бъдат сглобени или само А ще се?
Нишка
Виктор Бечев
22.04.2026 11:27

И двете _могат_, но когато итерираме ще върне първо `A`, ще изхаби двете руни и за `B` няма да останат.
Иван Грозданов
21.04.2026 15:14

Когато направим един runeword трябва ли да изтриваме използваните руни от руните които имаме, т.е. при този пример: calculator.add_runes(["Ber", "Ith", "Eld"]) print(next(iter(calculator))) # Enigma Накрая списъка с налични руни трябва да ни е празен или все още да съдържа "Ber", "Ith", "Eld"
Нишка
Виктор Бечев
22.04.2026 09:02

Да, руните се изчерпват.
Виктор Бечев
21.04.2026 08:29

Малка промяна, тъй като още с първото решение стана ясно, че не сме били достатъчно коректни в изказа си. Това: `Се връща следващият runeword, който все още не е бил върнат и може да бъде сглобен с руните, добавени досега.` Променяме на това: `Се връща първият по ред runeword измежду тези, които все още не са били върнати и могат да бъдат сглобени с руните, добавени досега.` С други думи - runewords по-рано в речника, не трябва да "блокират" създаването на други такива, които са по-назад в него. Можете да си мислите за "приоритет" а не за "фиксиран ред на връщане".