Предизвикателства > Валидатор за Никулденска рецепта > Решения > Решението на Йоан Байчев

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

1 точки общо

0 успешни теста
0 неуспешни теста
Код

  1import unittest
  2from unittest.mock import mock_open, patch
  3from secret import validate_recipe, RuinedNikuldenDinnerError
  4
  5def memoization(func): # Decorator to cache results of expensive function calls
  6    data = {}
  7
  8    def wrapper(*args):
  9        if args in data:
 10            return data[args]
 11        result = func(*args)
 12        data[args] = result
 13        return result
 14
 15    return wrapper
 16
 17@memoization
 18def generate_all_variations(word):
 19    size = len(word)
 20    result = []
 21    for i in range(1 << size): # Loop through all possible bitmasks for the word  
 22        current = []
 23        for j in range(size): # Loop through all letters (their binary representation)
 24            if i & (1 << j):  # Check if the j-th bit is 1 (is upper)
 25                current.append(word[j].upper())
 26            else:
 27                current.append(word[j].lower())
 28        result.append(''.join(current))
 29    return result
 30
 31@memoization
 32def generate_all_insertions_for_keyword(word, special_word):
 33    # Generate all insertions of special_word into the given word
 34    return [word[:i] + special_word + word[i:] for i in range(len(word) + 1)]
 35
 36@memoization
 37def generate_all_insertions_for_random(word, special_word):
 38    # Generate all insertions of word into the given special_word
 39    return [special_word[:i] + word + special_word[i:] for i in range(len(special_word) + 1)]
 40
 41
 42class TestNikuldenValidator(unittest.TestCase):
 43
 44    def setUp(self):
 45        self.keywords = ["риба", "рибена", "шаран", "сьонга"]
 46        self.special_word = "тр"
 47        self.valid_keywords = set()
 48        self.invalid_keywords = set()
 49        self.some_edge_cases = ["", "\t", "\n", "123", "!", "?", ".", ","]
 50
 51        self.templates = [
 52            "Днес ще ям {keyword}, защото е Никулден ;Д.",
 53            "Тази рецепта включва {keyword}.",
 54            "{keyword} е подходящо ястие за Никулден.",
 55            "Нямам против да приготвя {keyword}, знаейки че иначе ще бъде жена ми ;(.",
 56            "{keyword} е доста екзотично и полезно ястие.",
 57            "Практически {keyword} е клас {keyword} нали???",
 58            "{keyword}",
 59            "{keyword} ",
 60            " {keyword}",
 61            " {keyword} "
 62        ]
 63
 64        for current_keyword in self.keywords:
 65            variations = generate_all_variations(current_keyword)
 66            self.valid_keywords.update(variations)
 67
 68    def test_valid_recipe(self):
 69        valid_contents = [
 70            template.format(keyword=current_keyword)
 71            for current_keyword in self.valid_keywords
 72            for template in self.templates
 73        ]
 74
 75        for content in valid_contents:
 76            with self.subTest(content=content):
 77                m = mock_open(read_data=content)
 78                with patch("builtins.open", m):
 79                    result = validate_recipe("dummy_path.txt")
 80                    self.assertTrue(result, "Error")
 81
 82    def test_invalid_recipe(self):
 83        for current_keyword in self.valid_keywords:
 84            variations_keyword = generate_all_insertions_for_keyword(current_keyword, self.special_word)
 85            variations_random = generate_all_insertions_for_random(current_keyword, self.special_word)
 86            self.invalid_keywords.update(variations_keyword)
 87            self.invalid_keywords.update(variations_random)
 88
 89        invalid_contents = self.some_edge_cases.copy() # self.some_edge_cases is mutable
 90
 91        invalid_contents.extend(
 92            template.format(keyword=current_keyword)
 93            for current_keyword in self.invalid_keywords
 94            for template in self.templates
 95        )
 96
 97        for content in invalid_contents:
 98            with self.subTest(content=content):
 99                m = mock_open(read_data=content)
100                with patch("builtins.open", m):
101                    result = validate_recipe("dummy_path.txt")
102                    self.assertFalse(result, "Error")
103
104    def test_bad_recipe_file(self):
105        error_cases = [OSError, IOError]
106
107        for error in error_cases:
108            with self.subTest(error=error):
109                with patch("builtins.open", side_effect=error):
110                    with self.assertRaises(RuinedNikuldenDinnerError):
111                        validate_recipe("missing_file.txt")
112
113if __name__ == "__main__":
114    unittest.main()

Timed out.

Дискусия
Виктор Бечев
07.12.2024 13:30

Решението ти е доста изчерпателно, дори прекалено... Тестовете ти вървят ~22 секунди на моята машина, а нашите тестове, които тестват вашите тестове с различни имплементации на `validate_recipe` - минута и половина. Да не се опитвам да перифразирам, ето коментар, който оставих в друго решение: ``` Можеш да напишеш нещо сравнително кратко, което да ти прави въпросните пермутации, но истината е, че не е нужно. Алтернативен пример - ако имаме функция, която повдига четни числа на втора степен, нечетни - на трета и работи в отворен интервал от 0 до 100. Имаме да тестване за: Четно число. Нечетно число. Гранични стойности - 0 и 100. Не е нужно да тестваме с 2, 4, 6, 8 и т.н., достатъчно е да тестваме с едно четно число. Същото важи и за нечетните. Не е нужно да тестваме и за -5 и -500, достатъчно е да изтестваме поведението в граничните стойности - 0 и 100. Философията тук е, че да, винаги можеш да напишеш функция, която се чупи точно на 87. Но идеята не е да тестваме функциите, които пишем с всеки възможен input, а да генерализираме тестовете си спрямо изискванията, така че да могат да ни дадат достатъчно добра увереност, че функцията изпълнява дефинираните изисквания. Защото този проблем, който дефинирах е с елементарна сложност, но ако функцията ти е по-сложна или дори със същата сложност, но има граници от -сто милиона до +сто милиона - ще тестваш ли 200 000 002 пъти? Пък и да не забравяме невалидните стойности - те са безкрайно множество. ```
Йоан Байчев
05.12.2024 18:09

Като заключение бих казал, че моето решение се интересува от поведението на "правилните" думи ако те съществуват във файла, намиращи се около или в някоя част на друга дума, която реално няма значение стига да може да симулира 3-те случая (префикс, инфикс и суфикс), тоест да е поне 2-буквена.
Йоан Байчев
05.12.2024 18:05

Ако разгледаме думата "риба", тогава: self.valid_keywords = {'рибА', 'риба', 'Риба', 'РИба', 'рИба', 'РиБА', 'рИБа', 'рИбА', 'риБа', 'РибА', 'рИБА', 'РИБА', 'РИбА', 'риБА', 'РИБа', 'РиБа'} self.invalid_keywords = {'РИБатр', 'рибАтр', 'рИбтра', 'РтриБА', 'трИБАр', 'трибАр', 'РитрБа', 'РИбАтр', 'ртриБА', 'рИБтра', 'рибтра', 'трРиБа', 'ртрИба', 'тРибАр', 'трРИба', 'трРиба', 'риБАтр', 'РИбатр', 'рИБатр', 'РиБтра', 'тРИбАр', 'рИбтрА', 'ртрИбА', 'рИбатр', 'тррИбА', 'рИтрБа', 'РиБатр', 'РИбтрА', 'ритрба', 'РтрибА', 'РИБтра', 'трИБар', 'триБАр', 'Ртриба', 'тРИбар', 'рИбАтр', 'РтрИБА', 'тррибА', 'РИтрБа', 'ритрбА', 'риБтра', 'трРИБа', 'рИтрбА', 'тррИба', 'трИбАр', 'РтрИбА', 'РибтрА', 'РИбтра', 'РИБАтр', 'РиБАтр', 'рИБАтр', 'ртриба', 'трРибА', 'трРиБА', 'РтрИБа', 'тРИБАр', 'РИтрбА', 'рибтрА', 'трриБа', 'РИБтрА', 'тРиБар', 'рИтрБА', 'тРибар', 'ритрБа', 'ртрИБА', 'РитрбА', 'тррИБа', 'трИбар', 'Ритрба', 'РиБтрА', 'ритрБА', 'рИБтрА', 'трриба', 'тррИБА', 'рибатр', 'ртрибА', 'РтрИба', 'тРиБАр', 'трибар', 'РибАтр', 'ртрИБа', 'РтриБа', 'триБар', 'РитрБА', 'Рибатр', 'риБтрА', 'трРИБА', 'ртриБа', 'трриБА', 'РИтрБА', 'Рибтра', 'рИтрба', 'РИтрба', 'трРИбА', 'риБатр', 'тРИБар'}
Йоан Байчев
05.12.2024 17:51

1. Функцията generate_all_variations реално прави това тор -> {тор, Тор, тОр, тоР, ТОр, тОР, ТоР, ТОР}, генерира всички възможни думи, които ще върнат истина, като нали съответно тор е някоя от {"риба", "рибена", "шаран", "сьонга"}. Логиката ми е такава, на главна буква съпоставям 1 и на малка съпоставям 0, тоест с пример ТОР = 111, тоР = 001 или цялото би изглеждало така тор -> {000, 100, 010, 001, 110, 011, 101, 111} тоест за всяка буква в тор имаме две възможности 1 или 0 и тъй като тор има 3 букви следователно трябва да имаме 2^3 = 8 възможности, оттук идва логиката с for i in range(1 << size), тъй като 1 << size = 2 ^ size. С условието if i & (1 << j) проверяваме дали битът на позиция j в числото i е вдигнат (тоест е 1 ) и буквата е главна, като при примера с тор, i е някое число от {000, 100, 010, 001, 110, 011, 101, 111}, а j е съответно някое позиция от {0, 1, 2}, и съответно ако е 0 е малка буква. 2. Функцията generate_all_insertions_for_keyword генерира префикс, инфикс, суфикс, като word е някоя от думите {"риба", "рибена", "шаран", "сьонга"}, като е важно да се отбележи, че имам "константа" дума special_word, която така да го кажем винаги стои в средата. 3. Функцията generate_all_insertions_for_random генерира префикс, инфикс, суфикс, като word е някоя от думите {"риба", "рибена", "шаран", "сьонга"}, като тук "константата" дума е word, а special_word я обгръща(подобно на миналото, но на обратно) Изборът ми на special_word е "тр", мотивацията ми е че просто искам поне 2-буквена дума заради 3-те случая префикс, инфикс, суфикс и възможно най-малко сметки! 4. Функцията memoization е очевидна. В дадената задача с {"риба", "рибена", "шаран", "сьонга"} няма смисъл, но по-късна обработка и при добавяне на повтарящи се думи, било то умишлено или по грешка, това ще действа автоматизирано и ще ги "пренебрегва", като спестява време. Също така, ако по-късно към кода се добави допълнителна логика, която многократно извиква тези функции със същите параметри, кеширането ще спести време. Добавил съм some_edge_cases за празен файл, табулация, нов ред и други, като частни случаи, който може да бъде разширяван във времето. Създадох и templates, като удобен начин за добавяне на допълнителни тестове, без да се променя логиката на кода.
История

f1import unittestf1import unittest
2from unittest.mock import mock_open, patch2from unittest.mock import mock_open, patch
3from secret import validate_recipe, RuinedNikuldenDinnerError3from secret import validate_recipe, RuinedNikuldenDinnerError
44
5def memoization(func): # Decorator to cache results of expensive function calls5def memoization(func): # Decorator to cache results of expensive function calls
6    data = {}6    data = {}
77
8    def wrapper(*args):8    def wrapper(*args):
9        if args in data:9        if args in data:
10            return data[args]10            return data[args]
11        result = func(*args)11        result = func(*args)
12        data[args] = result12        data[args] = result
13        return result13        return result
1414
15    return wrapper15    return wrapper
1616
17@memoization17@memoization
18def generate_all_variations(word):18def generate_all_variations(word):
19    size = len(word)19    size = len(word)
20    result = []20    result = []
21    for i in range(1 << size): # Loop through all possible bitmasks for the word  21    for i in range(1 << size): # Loop through all possible bitmasks for the word  
22        current = []22        current = []
23        for j in range(size): # Loop through all letters (their binary representation)23        for j in range(size): # Loop through all letters (their binary representation)
24            if i & (1 << j):  # Check if the j-th bit is 1 (is upper)24            if i & (1 << j):  # Check if the j-th bit is 1 (is upper)
25                current.append(word[j].upper())25                current.append(word[j].upper())
26            else:26            else:
27                current.append(word[j].lower())27                current.append(word[j].lower())
28        result.append(''.join(current))28        result.append(''.join(current))
29    return result29    return result
3030
31@memoization31@memoization
32def generate_all_insertions_for_keyword(word, special_word):32def generate_all_insertions_for_keyword(word, special_word):
33    # Generate all insertions of special_word into the given word33    # Generate all insertions of special_word into the given word
34    return [word[:i] + special_word + word[i:] for i in range(len(word) + 1)]34    return [word[:i] + special_word + word[i:] for i in range(len(word) + 1)]
3535
36@memoization36@memoization
37def generate_all_insertions_for_random(word, special_word):37def generate_all_insertions_for_random(word, special_word):
38    # Generate all insertions of word into the given special_word38    # Generate all insertions of word into the given special_word
39    return [special_word[:i] + word + special_word[i:] for i in range(len(special_word) + 1)]39    return [special_word[:i] + word + special_word[i:] for i in range(len(special_word) + 1)]
4040
4141
42class TestNikuldenValidator(unittest.TestCase):42class TestNikuldenValidator(unittest.TestCase):
4343
44    def setUp(self):44    def setUp(self):
45        self.keywords = ["риба", "рибена", "шаран", "сьонга"]45        self.keywords = ["риба", "рибена", "шаран", "сьонга"]
46        self.special_word = "тр"46        self.special_word = "тр"
47        self.valid_keywords = set()47        self.valid_keywords = set()
48        self.invalid_keywords = set()48        self.invalid_keywords = set()
49        self.some_edge_cases = ["", "\t", "\n", "123", "!", "?", ".", ","]49        self.some_edge_cases = ["", "\t", "\n", "123", "!", "?", ".", ","]
5050
51        self.templates = [51        self.templates = [
52            "Днес ще ям {keyword}, защото е Никулден ;Д.",52            "Днес ще ям {keyword}, защото е Никулден ;Д.",
53            "Тази рецепта включва {keyword}.",53            "Тази рецепта включва {keyword}.",
54            "{keyword} е подходящо ястие за Никулден.",54            "{keyword} е подходящо ястие за Никулден.",
55            "Нямам против да приготвя {keyword}, знаейки че иначе ще бъде жена ми ;(.",55            "Нямам против да приготвя {keyword}, знаейки че иначе ще бъде жена ми ;(.",
t56            "{keyword} е доста екзотично и полезно ястие."t56            "{keyword} е доста екзотично и полезно ястие.",
57            "Практически {keyword} е клас {keyword} нали???"57            "Практически {keyword} е клас {keyword} нали???",
58            "{keyword}"58            "{keyword}",
59            "{keyword} "59            "{keyword} ",
60            " {keyword}"60            " {keyword}",
61            " {keyword} "61            " {keyword} "
62        ]62        ]
6363
64        for current_keyword in self.keywords:64        for current_keyword in self.keywords:
65            variations = generate_all_variations(current_keyword)65            variations = generate_all_variations(current_keyword)
66            self.valid_keywords.update(variations)66            self.valid_keywords.update(variations)
6767
68    def test_valid_recipe(self):68    def test_valid_recipe(self):
69        valid_contents = [69        valid_contents = [
70            template.format(keyword=current_keyword)70            template.format(keyword=current_keyword)
71            for current_keyword in self.valid_keywords71            for current_keyword in self.valid_keywords
72            for template in self.templates72            for template in self.templates
73        ]73        ]
7474
75        for content in valid_contents:75        for content in valid_contents:
76            with self.subTest(content=content):76            with self.subTest(content=content):
77                m = mock_open(read_data=content)77                m = mock_open(read_data=content)
78                with patch("builtins.open", m):78                with patch("builtins.open", m):
79                    result = validate_recipe("dummy_path.txt")79                    result = validate_recipe("dummy_path.txt")
80                    self.assertTrue(result, "Error")80                    self.assertTrue(result, "Error")
8181
82    def test_invalid_recipe(self):82    def test_invalid_recipe(self):
83        for current_keyword in self.valid_keywords:83        for current_keyword in self.valid_keywords:
84            variations_keyword = generate_all_insertions_for_keyword(current_keyword, self.special_word)84            variations_keyword = generate_all_insertions_for_keyword(current_keyword, self.special_word)
85            variations_random = generate_all_insertions_for_random(current_keyword, self.special_word)85            variations_random = generate_all_insertions_for_random(current_keyword, self.special_word)
86            self.invalid_keywords.update(variations_keyword)86            self.invalid_keywords.update(variations_keyword)
87            self.invalid_keywords.update(variations_random)87            self.invalid_keywords.update(variations_random)
8888
89        invalid_contents = self.some_edge_cases.copy() # self.some_edge_cases is mutable89        invalid_contents = self.some_edge_cases.copy() # self.some_edge_cases is mutable
9090
91        invalid_contents.extend(91        invalid_contents.extend(
92            template.format(keyword=current_keyword)92            template.format(keyword=current_keyword)
93            for current_keyword in self.invalid_keywords93            for current_keyword in self.invalid_keywords
94            for template in self.templates94            for template in self.templates
95        )95        )
9696
97        for content in invalid_contents:97        for content in invalid_contents:
98            with self.subTest(content=content):98            with self.subTest(content=content):
99                m = mock_open(read_data=content)99                m = mock_open(read_data=content)
100                with patch("builtins.open", m):100                with patch("builtins.open", m):
101                    result = validate_recipe("dummy_path.txt")101                    result = validate_recipe("dummy_path.txt")
102                    self.assertFalse(result, "Error")102                    self.assertFalse(result, "Error")
103103
104    def test_bad_recipe_file(self):104    def test_bad_recipe_file(self):
105        error_cases = [OSError, IOError]105        error_cases = [OSError, IOError]
106106
107        for error in error_cases:107        for error in error_cases:
108            with self.subTest(error=error):108            with self.subTest(error=error):
109                with patch("builtins.open", side_effect=error):109                with patch("builtins.open", side_effect=error):
110                    with self.assertRaises(RuinedNikuldenDinnerError):110                    with self.assertRaises(RuinedNikuldenDinnerError):
111                        validate_recipe("missing_file.txt")111                        validate_recipe("missing_file.txt")
112112
113if __name__ == "__main__":113if __name__ == "__main__":
114    unittest.main()114    unittest.main()
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

f1import unittestf1import unittest
2from unittest.mock import mock_open, patch2from unittest.mock import mock_open, patch
3from secret import validate_recipe, RuinedNikuldenDinnerError3from secret import validate_recipe, RuinedNikuldenDinnerError
44
5def memoization(func): # Decorator to cache results of expensive function calls5def memoization(func): # Decorator to cache results of expensive function calls
6    data = {}6    data = {}
77
8    def wrapper(*args):8    def wrapper(*args):
9        if args in data:9        if args in data:
10            return data[args]10            return data[args]
11        result = func(*args)11        result = func(*args)
12        data[args] = result12        data[args] = result
13        return result13        return result
1414
15    return wrapper15    return wrapper
1616
17@memoization17@memoization
18def generate_all_variations(word):18def generate_all_variations(word):
19    size = len(word)19    size = len(word)
20    result = []20    result = []
21    for i in range(1 << size): # Loop through all possible bitmasks for the word  21    for i in range(1 << size): # Loop through all possible bitmasks for the word  
22        current = []22        current = []
23        for j in range(size): # Loop through all letters (their binary representation)23        for j in range(size): # Loop through all letters (their binary representation)
24            if i & (1 << j):  # Check if the j-th bit is 1 (is upper)24            if i & (1 << j):  # Check if the j-th bit is 1 (is upper)
25                current.append(word[j].upper())25                current.append(word[j].upper())
26            else:26            else:
27                current.append(word[j].lower())27                current.append(word[j].lower())
28        result.append(''.join(current))28        result.append(''.join(current))
29    return result29    return result
3030
31@memoization31@memoization
32def generate_all_insertions_for_keyword(word, special_word):32def generate_all_insertions_for_keyword(word, special_word):
33    # Generate all insertions of special_word into the given word33    # Generate all insertions of special_word into the given word
34    return [word[:i] + special_word + word[i:] for i in range(len(word) + 1)]34    return [word[:i] + special_word + word[i:] for i in range(len(word) + 1)]
3535
36@memoization36@memoization
37def generate_all_insertions_for_random(word, special_word):37def generate_all_insertions_for_random(word, special_word):
38    # Generate all insertions of word into the given special_word38    # Generate all insertions of word into the given special_word
39    return [special_word[:i] + word + special_word[i:] for i in range(len(special_word) + 1)]39    return [special_word[:i] + word + special_word[i:] for i in range(len(special_word) + 1)]
4040
4141
42class TestNikuldenValidator(unittest.TestCase):42class TestNikuldenValidator(unittest.TestCase):
4343
44    def setUp(self):44    def setUp(self):
45        self.keywords = ["риба", "рибена", "шаран", "сьонга"]45        self.keywords = ["риба", "рибена", "шаран", "сьонга"]
46        self.special_word = "тр"46        self.special_word = "тр"
47        self.valid_keywords = set()47        self.valid_keywords = set()
48        self.invalid_keywords = set()48        self.invalid_keywords = set()
49        self.some_edge_cases = ["", "\t", "\n", "123", "!", "?", ".", ","]49        self.some_edge_cases = ["", "\t", "\n", "123", "!", "?", ".", ","]
5050
51        self.templates = [51        self.templates = [
52            "Днес ще ям {keyword}, защото е Никулден ;Д.",52            "Днес ще ям {keyword}, защото е Никулден ;Д.",
53            "Тази рецепта включва {keyword}.",53            "Тази рецепта включва {keyword}.",
54            "{keyword} е подходящо ястие за Никулден.",54            "{keyword} е подходящо ястие за Никулден.",
55            "Нямам против да приготвя {keyword}, знаейки че иначе ще бъде жена ми ;(.",55            "Нямам против да приготвя {keyword}, знаейки че иначе ще бъде жена ми ;(.",
56            "{keyword} е доста екзотично и полезно ястие."56            "{keyword} е доста екзотично и полезно ястие."
57            "Практически {keyword} е клас {keyword} нали???"57            "Практически {keyword} е клас {keyword} нали???"
tt58            "{keyword}"
59            "{keyword} "
60            " {keyword}"
61            " {keyword} "
58        ]62        ]
5963
60        for current_keyword in self.keywords:64        for current_keyword in self.keywords:
61            variations = generate_all_variations(current_keyword)65            variations = generate_all_variations(current_keyword)
62            self.valid_keywords.update(variations)66            self.valid_keywords.update(variations)
6367
64    def test_valid_recipe(self):68    def test_valid_recipe(self):
65        valid_contents = [69        valid_contents = [
66            template.format(keyword=current_keyword)70            template.format(keyword=current_keyword)
67            for current_keyword in self.valid_keywords71            for current_keyword in self.valid_keywords
68            for template in self.templates72            for template in self.templates
69        ]73        ]
7074
71        for content in valid_contents:75        for content in valid_contents:
72            with self.subTest(content=content):76            with self.subTest(content=content):
73                m = mock_open(read_data=content)77                m = mock_open(read_data=content)
74                with patch("builtins.open", m):78                with patch("builtins.open", m):
75                    result = validate_recipe("dummy_path.txt")79                    result = validate_recipe("dummy_path.txt")
76                    self.assertTrue(result, "Error")80                    self.assertTrue(result, "Error")
7781
78    def test_invalid_recipe(self):82    def test_invalid_recipe(self):
79        for current_keyword in self.valid_keywords:83        for current_keyword in self.valid_keywords:
80            variations_keyword = generate_all_insertions_for_keyword(current_keyword, self.special_word)84            variations_keyword = generate_all_insertions_for_keyword(current_keyword, self.special_word)
81            variations_random = generate_all_insertions_for_random(current_keyword, self.special_word)85            variations_random = generate_all_insertions_for_random(current_keyword, self.special_word)
82            self.invalid_keywords.update(variations_keyword)86            self.invalid_keywords.update(variations_keyword)
83            self.invalid_keywords.update(variations_random)87            self.invalid_keywords.update(variations_random)
8488
85        invalid_contents = self.some_edge_cases.copy() # self.some_edge_cases is mutable89        invalid_contents = self.some_edge_cases.copy() # self.some_edge_cases is mutable
8690
87        invalid_contents.extend(91        invalid_contents.extend(
88            template.format(keyword=current_keyword)92            template.format(keyword=current_keyword)
89            for current_keyword in self.invalid_keywords93            for current_keyword in self.invalid_keywords
90            for template in self.templates94            for template in self.templates
91        )95        )
9296
93        for content in invalid_contents:97        for content in invalid_contents:
94            with self.subTest(content=content):98            with self.subTest(content=content):
95                m = mock_open(read_data=content)99                m = mock_open(read_data=content)
96                with patch("builtins.open", m):100                with patch("builtins.open", m):
97                    result = validate_recipe("dummy_path.txt")101                    result = validate_recipe("dummy_path.txt")
98                    self.assertFalse(result, "Error")102                    self.assertFalse(result, "Error")
99103
100    def test_bad_recipe_file(self):104    def test_bad_recipe_file(self):
101        error_cases = [OSError, IOError]105        error_cases = [OSError, IOError]
102106
103        for error in error_cases:107        for error in error_cases:
104            with self.subTest(error=error):108            with self.subTest(error=error):
105                with patch("builtins.open", side_effect=error):109                with patch("builtins.open", side_effect=error):
106                    with self.assertRaises(RuinedNikuldenDinnerError):110                    with self.assertRaises(RuinedNikuldenDinnerError):
107                        validate_recipe("missing_file.txt")111                        validate_recipe("missing_file.txt")
108112
109if __name__ == "__main__":113if __name__ == "__main__":
110    unittest.main()114    unittest.main()
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

f1import unittestf1import unittest
2from unittest.mock import mock_open, patch2from unittest.mock import mock_open, patch
3from secret import validate_recipe, RuinedNikuldenDinnerError3from secret import validate_recipe, RuinedNikuldenDinnerError
44
n5def memoization(func):n5def memoization(func): # Decorator to cache results of expensive function calls
6    data = {}6    data = {}
77
8    def wrapper(*args):8    def wrapper(*args):
9        if args in data:9        if args in data:
10            return data[args]10            return data[args]
11        result = func(*args)11        result = func(*args)
12        data[args] = result12        data[args] = result
13        return result13        return result
1414
15    return wrapper15    return wrapper
1616
17@memoization17@memoization
18def generate_all_variations(word):18def generate_all_variations(word):
19    size = len(word)19    size = len(word)
20    result = []20    result = []
n21    for i in range(1 << size):  n21    for i in range(1 << size): # Loop through all possible bitmasks for the word  
22        current = []22        current = []
n23        for j in range(size):n23        for j in range(size): # Loop through all letters (their binary representation)
24            if i & (1 << j):  24            if i & (1 << j):  # Check if the j-th bit is 1 (is upper)
25                current.append(word[j].upper())25                current.append(word[j].upper())
26            else:26            else:
27                current.append(word[j].lower())27                current.append(word[j].lower())
28        result.append(''.join(current))28        result.append(''.join(current))
29    return result29    return result
3030
31@memoization31@memoization
32def generate_all_insertions_for_keyword(word, special_word):32def generate_all_insertions_for_keyword(word, special_word):
nn33    # Generate all insertions of special_word into the given word
33    return [word[:i] + special_word + word[i:] for i in range(len(word) + 1)]34    return [word[:i] + special_word + word[i:] for i in range(len(word) + 1)]
3435
35@memoization36@memoization
36def generate_all_insertions_for_random(word, special_word):37def generate_all_insertions_for_random(word, special_word):
nn38    # Generate all insertions of word into the given special_word
37    return [special_word[:i] + word + special_word[i:] for i in range(len(special_word) + 1)]39    return [special_word[:i] + word + special_word[i:] for i in range(len(special_word) + 1)]
3840
3941
40class TestNikuldenValidator(unittest.TestCase):42class TestNikuldenValidator(unittest.TestCase):
4143
42    def setUp(self):44    def setUp(self):
43        self.keywords = ["риба", "рибена", "шаран", "сьонга"]45        self.keywords = ["риба", "рибена", "шаран", "сьонга"]
44        self.special_word = "тр"46        self.special_word = "тр"
45        self.valid_keywords = set()47        self.valid_keywords = set()
46        self.invalid_keywords = set()48        self.invalid_keywords = set()
47        self.some_edge_cases = ["", "\t", "\n", "123", "!", "?", ".", ","]49        self.some_edge_cases = ["", "\t", "\n", "123", "!", "?", ".", ","]
4850
49        self.templates = [51        self.templates = [
50            "Днес ще ям {keyword}, защото е Никулден ;Д.",52            "Днес ще ям {keyword}, защото е Никулден ;Д.",
51            "Тази рецепта включва {keyword}.",53            "Тази рецепта включва {keyword}.",
52            "{keyword} е подходящо ястие за Никулден.",54            "{keyword} е подходящо ястие за Никулден.",
53            "Нямам против да приготвя {keyword}, знаейки че иначе ще бъде жена ми ;(.",55            "Нямам против да приготвя {keyword}, знаейки че иначе ще бъде жена ми ;(.",
54            "{keyword} е доста екзотично и полезно ястие."56            "{keyword} е доста екзотично и полезно ястие."
55            "Практически {keyword} е клас {keyword} нали???"57            "Практически {keyword} е клас {keyword} нали???"
56        ]58        ]
5759
58        for current_keyword in self.keywords:60        for current_keyword in self.keywords:
59            variations = generate_all_variations(current_keyword)61            variations = generate_all_variations(current_keyword)
60            self.valid_keywords.update(variations)62            self.valid_keywords.update(variations)
6163
62    def test_valid_recipe(self):64    def test_valid_recipe(self):
63        valid_contents = [65        valid_contents = [
64            template.format(keyword=current_keyword)66            template.format(keyword=current_keyword)
65            for current_keyword in self.valid_keywords67            for current_keyword in self.valid_keywords
66            for template in self.templates68            for template in self.templates
67        ]69        ]
6870
69        for content in valid_contents:71        for content in valid_contents:
70            with self.subTest(content=content):72            with self.subTest(content=content):
71                m = mock_open(read_data=content)73                m = mock_open(read_data=content)
72                with patch("builtins.open", m):74                with patch("builtins.open", m):
73                    result = validate_recipe("dummy_path.txt")75                    result = validate_recipe("dummy_path.txt")
74                    self.assertTrue(result, "Error")76                    self.assertTrue(result, "Error")
7577
76    def test_invalid_recipe(self):78    def test_invalid_recipe(self):
77        for current_keyword in self.valid_keywords:79        for current_keyword in self.valid_keywords:
78            variations_keyword = generate_all_insertions_for_keyword(current_keyword, self.special_word)80            variations_keyword = generate_all_insertions_for_keyword(current_keyword, self.special_word)
79            variations_random = generate_all_insertions_for_random(current_keyword, self.special_word)81            variations_random = generate_all_insertions_for_random(current_keyword, self.special_word)
80            self.invalid_keywords.update(variations_keyword)82            self.invalid_keywords.update(variations_keyword)
81            self.invalid_keywords.update(variations_random)83            self.invalid_keywords.update(variations_random)
8284
t83        invalid_contents = self.some_edge_cases.copy()t85        invalid_contents = self.some_edge_cases.copy() # self.some_edge_cases is mutable
8486
85        invalid_contents.extend(87        invalid_contents.extend(
86            template.format(keyword=current_keyword)88            template.format(keyword=current_keyword)
87            for current_keyword in self.invalid_keywords89            for current_keyword in self.invalid_keywords
88            for template in self.templates90            for template in self.templates
89        )91        )
9092
91        for content in invalid_contents:93        for content in invalid_contents:
92            with self.subTest(content=content):94            with self.subTest(content=content):
93                m = mock_open(read_data=content)95                m = mock_open(read_data=content)
94                with patch("builtins.open", m):96                with patch("builtins.open", m):
95                    result = validate_recipe("dummy_path.txt")97                    result = validate_recipe("dummy_path.txt")
96                    self.assertFalse(result, "Error")98                    self.assertFalse(result, "Error")
9799
98    def test_bad_recipe_file(self):100    def test_bad_recipe_file(self):
99        error_cases = [OSError, IOError]101        error_cases = [OSError, IOError]
100102
101        for error in error_cases:103        for error in error_cases:
102            with self.subTest(error=error):104            with self.subTest(error=error):
103                with patch("builtins.open", side_effect=error):105                with patch("builtins.open", side_effect=error):
104                    with self.assertRaises(RuinedNikuldenDinnerError):106                    with self.assertRaises(RuinedNikuldenDinnerError):
105                        validate_recipe("missing_file.txt")107                        validate_recipe("missing_file.txt")
106108
107if __name__ == "__main__":109if __name__ == "__main__":
108    unittest.main()110    unittest.main()
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

f1import unittestf1import unittest
2from unittest.mock import mock_open, patch2from unittest.mock import mock_open, patch
3from secret import validate_recipe, RuinedNikuldenDinnerError3from secret import validate_recipe, RuinedNikuldenDinnerError
44
5def memoization(func):5def memoization(func):
6    data = {}6    data = {}
77
8    def wrapper(*args):8    def wrapper(*args):
9        if args in data:9        if args in data:
10            return data[args]10            return data[args]
11        result = func(*args)11        result = func(*args)
12        data[args] = result12        data[args] = result
13        return result13        return result
1414
15    return wrapper15    return wrapper
1616
17@memoization17@memoization
18def generate_all_variations(word):18def generate_all_variations(word):
19    size = len(word)19    size = len(word)
20    result = []20    result = []
21    for i in range(1 << size):  21    for i in range(1 << size):  
22        current = []22        current = []
23        for j in range(size):23        for j in range(size):
24            if i & (1 << j):  24            if i & (1 << j):  
25                current.append(word[j].upper())25                current.append(word[j].upper())
26            else:26            else:
27                current.append(word[j].lower())27                current.append(word[j].lower())
28        result.append(''.join(current))28        result.append(''.join(current))
29    return result29    return result
3030
31@memoization31@memoization
32def generate_all_insertions_for_keyword(word, special_word):32def generate_all_insertions_for_keyword(word, special_word):
33    return [word[:i] + special_word + word[i:] for i in range(len(word) + 1)]33    return [word[:i] + special_word + word[i:] for i in range(len(word) + 1)]
3434
35@memoization35@memoization
36def generate_all_insertions_for_random(word, special_word):36def generate_all_insertions_for_random(word, special_word):
37    return [special_word[:i] + word + special_word[i:] for i in range(len(special_word) + 1)]37    return [special_word[:i] + word + special_word[i:] for i in range(len(special_word) + 1)]
3838
3939
40class TestNikuldenValidator(unittest.TestCase):40class TestNikuldenValidator(unittest.TestCase):
4141
42    def setUp(self):42    def setUp(self):
43        self.keywords = ["риба", "рибена", "шаран", "сьонга"]43        self.keywords = ["риба", "рибена", "шаран", "сьонга"]
t44        self.special_word = "рок"t44        self.special_word = "тр"
45        self.valid_keywords = set()45        self.valid_keywords = set()
46        self.invalid_keywords = set()46        self.invalid_keywords = set()
47        self.some_edge_cases = ["", "\t", "\n", "123", "!", "?", ".", ","]47        self.some_edge_cases = ["", "\t", "\n", "123", "!", "?", ".", ","]
4848
49        self.templates = [49        self.templates = [
50            "Днес ще ям {keyword}, защото е Никулден ;Д.",50            "Днес ще ям {keyword}, защото е Никулден ;Д.",
51            "Тази рецепта включва {keyword}.",51            "Тази рецепта включва {keyword}.",
52            "{keyword} е подходящо ястие за Никулден.",52            "{keyword} е подходящо ястие за Никулден.",
53            "Нямам против да приготвя {keyword}, знаейки че иначе ще бъде жена ми ;(.",53            "Нямам против да приготвя {keyword}, знаейки че иначе ще бъде жена ми ;(.",
54            "{keyword} е доста екзотично и полезно ястие."54            "{keyword} е доста екзотично и полезно ястие."
55            "Практически {keyword} е клас {keyword} нали???"55            "Практически {keyword} е клас {keyword} нали???"
56        ]56        ]
5757
58        for current_keyword in self.keywords:58        for current_keyword in self.keywords:
59            variations = generate_all_variations(current_keyword)59            variations = generate_all_variations(current_keyword)
60            self.valid_keywords.update(variations)60            self.valid_keywords.update(variations)
6161
62    def test_valid_recipe(self):62    def test_valid_recipe(self):
63        valid_contents = [63        valid_contents = [
64            template.format(keyword=current_keyword)64            template.format(keyword=current_keyword)
65            for current_keyword in self.valid_keywords65            for current_keyword in self.valid_keywords
66            for template in self.templates66            for template in self.templates
67        ]67        ]
6868
69        for content in valid_contents:69        for content in valid_contents:
70            with self.subTest(content=content):70            with self.subTest(content=content):
71                m = mock_open(read_data=content)71                m = mock_open(read_data=content)
72                with patch("builtins.open", m):72                with patch("builtins.open", m):
73                    result = validate_recipe("dummy_path.txt")73                    result = validate_recipe("dummy_path.txt")
74                    self.assertTrue(result, "Error")74                    self.assertTrue(result, "Error")
7575
76    def test_invalid_recipe(self):76    def test_invalid_recipe(self):
77        for current_keyword in self.valid_keywords:77        for current_keyword in self.valid_keywords:
78            variations_keyword = generate_all_insertions_for_keyword(current_keyword, self.special_word)78            variations_keyword = generate_all_insertions_for_keyword(current_keyword, self.special_word)
79            variations_random = generate_all_insertions_for_random(current_keyword, self.special_word)79            variations_random = generate_all_insertions_for_random(current_keyword, self.special_word)
80            self.invalid_keywords.update(variations_keyword)80            self.invalid_keywords.update(variations_keyword)
81            self.invalid_keywords.update(variations_random)81            self.invalid_keywords.update(variations_random)
8282
83        invalid_contents = self.some_edge_cases.copy()83        invalid_contents = self.some_edge_cases.copy()
8484
85        invalid_contents.extend(85        invalid_contents.extend(
86            template.format(keyword=current_keyword)86            template.format(keyword=current_keyword)
87            for current_keyword in self.invalid_keywords87            for current_keyword in self.invalid_keywords
88            for template in self.templates88            for template in self.templates
89        )89        )
9090
91        for content in invalid_contents:91        for content in invalid_contents:
92            with self.subTest(content=content):92            with self.subTest(content=content):
93                m = mock_open(read_data=content)93                m = mock_open(read_data=content)
94                with patch("builtins.open", m):94                with patch("builtins.open", m):
95                    result = validate_recipe("dummy_path.txt")95                    result = validate_recipe("dummy_path.txt")
96                    self.assertFalse(result, "Error")96                    self.assertFalse(result, "Error")
9797
98    def test_bad_recipe_file(self):98    def test_bad_recipe_file(self):
99        error_cases = [OSError, IOError]99        error_cases = [OSError, IOError]
100100
101        for error in error_cases:101        for error in error_cases:
102            with self.subTest(error=error):102            with self.subTest(error=error):
103                with patch("builtins.open", side_effect=error):103                with patch("builtins.open", side_effect=error):
104                    with self.assertRaises(RuinedNikuldenDinnerError):104                    with self.assertRaises(RuinedNikuldenDinnerError):
105                        validate_recipe("missing_file.txt")105                        validate_recipe("missing_file.txt")
106106
107if __name__ == "__main__":107if __name__ == "__main__":
108    unittest.main()108    unittest.main()
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op