Repository: andreypomortsev/yndx-python-handbook

Issue:
Feature: Add 5 solutions for 4.2 F-J
1. Выложить решения для задач F,G,H,I,J параграфа 4.2
2. Покрыть тестами 100% решений, обновить README.md.

Hints from issue comments:
@Eugene-b-y-t-e: 42g не будет правильно работать, если не вызвать get_sum
т.е.
enter_results(1, 2, 3, 4, 5, 6)
print(get_sum(), get_average())
совпадет с решением

enter_results(1, 2, 3, 4, 5, 6)
print(get_average())
вызовет ошибку, т.к. total не определена

думаю правильнее будет перенести подсчет сумм и количества в enter_results

Solution patch:
diff --git a/.coveragerc b/.coveragerc
index 343cc1a9..9972df90 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -9,6 +9,8 @@ omit =
     constants.py
     tests/data/*
     test_*
+    solutions/4.2/42_h.py
+    solutions/4.2/42_i.py
 
 [report]
 show_missing = True
diff --git a/README.md b/README.md
index d82c94ff..07da973e 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ _"Хендбук по Python поможет овладеть основным с
 ### Описание Проекта
 
 Решения написаны мной, но некоторые тестовые данные могут быть позаимствованы из [одноименного](https://t.me/handbook_python) телеграм канала. Решение задач подразумевает использование знаний полученных после изучения соответствующего параграфа.
-Так же я решил покрыть тестами все свои решения, тесты не совпадают на 100% с тестами в учебнике, но при замене моего решения на ваш код, можно будет понять на каких данных не работает код или какие строчки не покрыты тестами (эти строки могут быть лишними). 
+Так же я решил покрыть тестами все свои решения, тесты не совпадают на 100% с тестами в учебнике, но при замене моего решения на ваш код, можно будет понять на каких данных не работает код или какие строчки не покрыты тестами (эти строки могут быть лишними).
 
 ## Установка
 
@@ -78,11 +78,11 @@ make test-dir-2.1
 
 - На macOS и Linux:
 
-  Тест одной задачи (вместо `2.3` укажите нужный параграф, вместо `Q` или `q` укажите тест для нужной задачи) приведенный код запустит тесты для задачи **Q. Чётная чистота**  из парагарафа [2.3](./solutions/2.3/). 
+  Тест одной задачи (вместо `2.3` укажите нужный параграф, вместо `Q` или `q` укажите тест для нужной задачи) приведенный код запустит тесты для задачи **Q. Чётная чистота**  из парагарафа [2.3](./solutions/2.3/).
   
   **Важно**
   
-  Команда запускает [скрипт](fileTest.sh), рекомендую проверить его перед запуском команды, если его содержимое вам не понятно - используйте способ тестирования для __Windows__
+  Команда запускает [скрипт](fileTest.sh), рекомендую проверить его перед запуском команды, если его содержимое вам не понятно - используйте способ тестирования для **Windows**
   
   ```sh
   make test-file-2.3-Q
@@ -184,7 +184,7 @@ def main():
           print("No solution")
 ```
 
-После того как отчет по покрытию готов, файлы удаляются. 
+После того как отчет по покрытию готов, файлы удаляются.
 
 </details>
 
@@ -204,6 +204,7 @@ def main():
 ## Решения | Тесты
 
 <details>
+
 <summary><h3>2.1 Ввод и вывод данных. Операции с числами, строками. Форматирование</h3></summary>
 
 - [2.1 Ввод и вывод данных. Операции с числами, строками. Форматирование](https://education.yandex.ru/handbook/python/article/vvod-i-vyvod-dannykh-operatsii-s-chislami-strokami-formatirovaniye)
@@ -517,11 +518,11 @@ def main():
 | C. [Функциональный нод 2.0](./solutions/4.2/42_c.py) | [✅](./tests/4.2/test_42_c.py) |
 | D. [Имя of the month 2.0](./solutions/4.2/42_d.py) | [✅](./tests/4.2/test_42_d.py) |
 | E. [Подготовка данных](./solutions/4.2/42_e.py) | [✅](./tests/4.2/test_42_e.py) |
-| F. [Кофейня](./solutions/4.2/42_f.py) | [❌](./tests/4.2/test_42_f.py) |
-| G. [В эфире рубрика «Эксперименты»](./solutions/4.2/42_g.py) | [❌](./tests/4.2/test_42_g.py) |
-| H. [Длинная сортировка](./solutions/4.2/42_h.py) | [❌](./tests/4.2/test_42_h.py) |
-| I. [Чётная фильтрация](./solutions/4.2/42_i.py) | [❌](./tests/4.2/test_42_i.py) |
-| J. [Ключевой секрет](./solutions/4.2/42_j.py) | [❌](./tests/4.2/test_42_j.py) |
+| F. [Кофейня](./solutions/4.2/42_f.py) | [✅](./tests/4.2/test_42_f.py) |
+| G. [В эфире рубрика «Эксперименты»](./solutions/4.2/42_g.py) | [✅](./tests/4.2/test_42_g.py) |
+| H. [Длинная сортировка](./solutions/4.2/42_h.py) | [✅](./tests/4.2/test_42_h.py) |
+| I. [Чётная фильтрация](./solutions/4.2/42_i.py) | [✅](./tests/4.2/test_42_i.py) |
+| J. [Ключевой секрет](./solutions/4.2/42_j.py) | [✅](./tests/4.2/test_42_j.py) |
 
 </details>
 
@@ -531,34 +532,34 @@ def main():
 
 ### Шаблон для Issue
 
-#### Описание ошибки:
+#### Описание ошибки
 
 - Краткое описание проблемы.
 
-#### Шаги для воспроизведения:
+#### Шаги для воспроизведения
 
 1. Опишите последовательность шагов, которые привели к появлению ошибки.
 2. Укажите точные команды, скрипты или действия, названия файла(ов) если возможно.
 
-#### Ожидаемое поведение:
+#### Ожидаемое поведение
 
 - Опишите, как все должно работать при нормальных условиях.
 
-#### Фактическое поведение:
+#### Фактическое поведение
 
-- Опишите, что происходит на самом деле. 
+- Опишите, что происходит на самом деле.
 
-#### Контекст и окружение:
+#### Контекст и окружение
 
 - Операционная система и версия.
 - Версия используемого ПО (Python, VSCode, PyCharm, etc.).
 - Укажите любые установленные зависимости (например, версии библиотек).
 
-#### Логи и скриншоты:
+#### Логи и скриншоты
 
 - Прикрепите любые ошибки из логов или скриншоты, если это применимо.
 
-#### Дополнительная информация:
+#### Дополнительная информация
 
 - Любые другие важные детали, которые помогут в исправлении ошибки.
 
diff --git a/solutions/2.2/22_p.py b/solutions/2.2/22_p.py
index 6c542f02..87e27c4a 100644
--- a/solutions/2.2/22_p.py
+++ b/solutions/2.2/22_p.py
@@ -9,9 +9,11 @@
 if speed_one < speed_two:
     speed_one, speed_two = speed_two, speed_one
     name_one, name_two = name_two, name_one
+
 if speed_one < speed_three:
     speed_one, speed_three = speed_three, speed_one
     name_one, name_three = name_three, name_one
+
 if speed_two < speed_three:
     speed_two, speed_three = speed_three, speed_two
     name_two, name_three = name_three, name_two
diff --git a/solutions/2.3/23_q.py b/solutions/2.3/23_q.py
index 2cc82838..de0970b7 100644
--- a/solutions/2.3/23_q.py
+++ b/solutions/2.3/23_q.py
@@ -1,5 +1,13 @@
-numbers = input()
+numbers = int(input())
 
-for digit in numbers:
-    if int(digit) % 2 != 0:
-        print(digit, end="")
+clean_number = 0
+position = 1
+
+while numbers > 0:
+    digit = numbers % 10
+    if digit % 2 != 0:
+        clean_number += digit * position
+        position *= 10
+    numbers //= 10
+
+print(clean_number)
diff --git a/solutions/2.3/g23.py b/solutions/2.3/g23.py
deleted file mode 100644
index 50dfea20..00000000
--- a/solutions/2.3/g23.py
+++ /dev/null
@@ -1,17 +0,0 @@
-a = int(input())
-b = int(input())
-
-a_copy = a
-b_copy = b
-
-if not a:
-    gcd = b
-elif not b:
-    gcd = a
-else:
-    while b:
-        a, b = b, a % b
-    gcd = a
-
-lcm = (a_copy * b_copy) // gcd
-print(lcm)
diff --git a/solutions/4.2/42_f.py b/solutions/4.2/42_f.py
new file mode 100644
index 00000000..fc68a75a
--- /dev/null
+++ b/solutions/4.2/42_f.py
@@ -0,0 +1,39 @@
+from threading import Lock
+from typing import Dict
+
+IN_STOK_LOCK = Lock()
+RECIPIES = {
+    "Эспрессо": {"coffee": 1, "milk": 0, "cream": 0},
+    "Капучино": {"coffee": 1, "milk": 3, "cream": 0},
+    "Макиато": {"coffee": 2, "milk": 1, "cream": 0},
+    "Кофе по-венски": {"coffee": 1, "milk": 0, "cream": 2},
+    "Латте Макиато": {"coffee": 1, "milk": 2, "cream": 1},
+    "Кон Панна": {"coffee": 1, "milk": 0, "cream": 1},
+}
+
+
+def check_ingredients(ingredients: Dict[str, int]) -> bool:
+    """Проверка достаточности ингредиентов для напитка"""
+    with IN_STOK_LOCK:
+        return all(
+            in_stock.get(ingredient, 0) >= quantity  # noqa F821
+            for ingredient, quantity in ingredients.items()
+        )
+
+
+def update_stock(ingredients: Dict[str, int]) -> None:
+    """Обновление ингредиентов в остатках"""
+    with IN_STOK_LOCK:
+        for ingredient, quantity in ingredients.items():
+            in_stock[ingredient] -= quantity  # noqa F821
+
+
+def order(*preferences) -> str:
+    for drink in preferences:
+        ingredients = RECIPIES.get(drink, None)
+
+        if ingredients and check_ingredients(ingredients):
+            update_stock(ingredients)
+            return drink
+
+    return "К сожалению, не можем предложить Вам напиток"
diff --git a/solutions/4.2/42_g.py b/solutions/4.2/42_g.py
new file mode 100644
index 00000000..8008a9db
--- /dev/null
+++ b/solutions/4.2/42_g.py
@@ -0,0 +1,32 @@
+from typing import Tuple
+
+
+def enter_results(*pairs) -> Tuple[float]:
+    global results
+
+    if "results" in globals():
+        results.extend(list(pairs))
+    else:
+        results = list(pairs)
+
+
+def get_sum() -> Tuple[float]:
+    global results
+    global total
+
+    total = list(results[:2])
+
+    for i in range(2, len(results), 2):
+        total[i % 2] += results[i]
+        total[(i + 1) % 2] += results[i + 1]
+
+    return round(total[0], 2), round(total[1], 2)
+
+
+def get_average() -> Tuple[float]:
+    global results
+    global total
+
+    length = len(results) // 2
+
+    return tuple(round(total[i] / length, 2) for i in range(2))
diff --git a/solutions/4.2/42_h.py b/solutions/4.2/42_h.py
new file mode 100644
index 00000000..86201c77
--- /dev/null
+++ b/solutions/4.2/42_h.py
@@ -0,0 +1 @@
+lambda x: (len(x), x.lower())
diff --git a/solutions/4.2/42_i.py b/solutions/4.2/42_i.py
new file mode 100644
index 00000000..c2df5266
--- /dev/null
+++ b/solutions/4.2/42_i.py
@@ -0,0 +1 @@
+lambda x: not sum(map(int, str(x))) % 2
diff --git a/solutions/4.2/42_j.py b/solutions/4.2/42_j.py
new file mode 100644
index 00000000..d708587a
--- /dev/null
+++ b/solutions/4.2/42_j.py
@@ -0,0 +1,21 @@
+def secret_replace(text: str, **replaces) -> str:
+    repeats = {}
+    encoded = []
+
+    for char in text:
+        if char in replaces:
+            if char not in repeats:
+                repeats[char] = 0
+            elif repeats[char] < len(replaces[char]) - 1:
+                repeats[char] += 1
+            else:
+                repeats[char] = 0
+
+            encoded_char_index = repeats[char]
+            encoded_char = replaces[char][encoded_char_index]
+            encoded.append(encoded_char)
+
+        else:
+            encoded.append(char)
+
+    return "".join(encoded)


Test patch:
diff --git a/tests/4.2/test_42_f.py b/tests/4.2/test_42_f.py
new file mode 100644
index 00000000..e3e1a92d
--- /dev/null
+++ b/tests/4.2/test_42_f.py
@@ -0,0 +1,34 @@
+from types import ModuleType
+from typing import Callable, Dict, Tuple
+
+import pytest
+
+from tests import utils
+from tests.conftest import MEMORY_LIMIT, TIME_LIMIT
+from tests.data.test_data_42 import f_test_data
+
+
+@pytest.mark.parametrize(
+    "in_stock_values, args, expected_outputs, _",
+    f_test_data,
+    ids=[i[-1] for i in f_test_data],
+)
+def test_order(
+    request: pytest.FixtureRequest,
+    load_module: Callable[[str], ModuleType],
+    in_stock_values: Dict[str, int],
+    args: Tuple[Tuple[str]],
+    expected_outputs: Tuple[Tuple[str]],
+    _: str,
+) -> None:
+    file_path, _ = utils.get_tested_file_details(request)
+    solution_module = load_module(file_path)
+
+    setattr(solution_module, "in_stock", in_stock_values)
+
+    decorated_func = utils.memory_limit(MEMORY_LIMIT)(solution_module.order)
+    decorated_func = utils.time_limit(TIME_LIMIT)(decorated_func)
+
+    for arg, expected_output in zip(args, expected_outputs):
+        returned_output = decorated_func(*arg)
+        assert returned_output == expected_output
diff --git a/tests/4.2/test_42_g.py b/tests/4.2/test_42_g.py
new file mode 100644
index 00000000..e8b0e4e9
--- /dev/null
+++ b/tests/4.2/test_42_g.py
@@ -0,0 +1,48 @@
+from types import ModuleType
+from typing import Callable, Tuple, Union
+
+import pytest
+
+from tests import utils
+from tests.constants import MEMORY_LIMIT, TIME_LIMIT
+from tests.data.test_data_42 import g_test_data
+
+
+@pytest.mark.parametrize(
+    "results, expected_outputs, _",
+    g_test_data,
+    ids=[i[-1] for i in g_test_data],
+)
+def test_enter_results_get_sum_average(
+    request: pytest.FixtureRequest,
+    load_module: Callable[[str], ModuleType],
+    results: Tuple[Tuple[Union[int, float]]],
+    expected_outputs: Tuple[Tuple[Union[int, float]]],
+    _: str,
+) -> None:
+    file_path, _ = utils.get_tested_file_details(request)
+    solution = load_module(file_path)
+
+    @utils.memory_limit(MEMORY_LIMIT)
+    @utils.time_limit(TIME_LIMIT)
+    def solution_wrapper(
+        result: Tuple[Union[int, float]]
+    ) -> Tuple[Union[int, float]]:
+        """
+        Оборачиваем исполнение функции в таймер и лимит памяти
+        для отслеживания превышений.
+
+        Возвращает:
+            get_sum, get_average (Tuple[Union[int, float]]):
+            Возвращает результаты выполнения функций get_sum и get_average
+        """
+        solution.enter_results(*result)
+        return solution.get_sum(), solution.get_average()
+
+    for result, expected_output in zip(results, expected_outputs):
+        expected_get_sum, expected_get_average = expected_output
+
+        returned_get_sum, returned_get_average = solution_wrapper(result)
+
+        assert returned_get_sum == expected_get_sum
+        assert returned_get_average == expected_get_average
diff --git a/tests/4.2/test_42_h.py b/tests/4.2/test_42_h.py
new file mode 100644
index 00000000..eb85af6f
--- /dev/null
+++ b/tests/4.2/test_42_h.py
@@ -0,0 +1,49 @@
+from types import FunctionType
+from typing import Dict, List, Tuple
+
+import pytest
+
+from tests import utils
+from tests.constants import MEMORY_LIMIT, TIME_LIMIT
+from tests.data.test_data_42 import h_test_data
+
+
+@pytest.mark.parametrize(
+    "string, expected_output, _",
+    h_test_data,
+    ids=[i[-1] for i in h_test_data],
+)
+def test_sort_lambda(
+    request: pytest.FixtureRequest,
+    string: str,
+    expected_output: List[str],
+    _: str,
+) -> None:
+    file_path, _ = utils.get_tested_file_details(request)
+
+    with open(file_path, "r", encoding="UTF-8") as solution_file:
+        lines = solution_file.readlines()
+        solution = "".join(lines)
+
+    if not solution.startswith("lambda"):
+        raise SyntaxError("Compilation error (CE)")
+
+    @utils.memory_limit(MEMORY_LIMIT)
+    @utils.time_limit(TIME_LIMIT)
+    def solution_wrapper(
+        str_lambda: str, string: str, calls: Dict[str, FunctionType]
+    ) -> Tuple[int]:
+        """
+        Оборачиваем исполнение функции в таймер и лимит памяти
+        для отслеживания превышений.
+
+        Возвращает:
+            (Tuple[int]):
+            Возвращает результаты выполнения функций sorted + lambda
+        """
+        return sorted(string.split(), key=eval(str_lambda, calls))
+
+    safe_calls = {"len": len}
+    returned_output = solution_wrapper(solution, string, safe_calls)
+
+    assert returned_output == expected_output
diff --git a/tests/4.2/test_42_i.py b/tests/4.2/test_42_i.py
new file mode 100644
index 00000000..f86a6e09
--- /dev/null
+++ b/tests/4.2/test_42_i.py
@@ -0,0 +1,49 @@
+from types import FunctionType
+from typing import Dict, Tuple
+
+import pytest
+
+from tests import utils
+from tests.constants import MEMORY_LIMIT, TIME_LIMIT
+from tests.data.test_data_42 import i_test_data
+
+
+@pytest.mark.parametrize(
+    "input, expected_output, _",
+    i_test_data,
+    ids=[i[-1] for i in i_test_data],
+)
+def test_filter_lambda(
+    request: pytest.FixtureRequest,
+    input: Tuple[int],
+    expected_output: Tuple[str],
+    _: str,
+) -> None:
+    file_path, _ = utils.get_tested_file_details(request)
+
+    with open(file_path, "r", encoding="UTF-8") as solution_file:
+        lines = solution_file.readlines()
+        solution = "".join(lines)
+
+    if not solution.startswith("lambda"):
+        raise SyntaxError("Compilation error (CE)")
+
+    @utils.memory_limit(MEMORY_LIMIT)
+    @utils.time_limit(TIME_LIMIT)
+    def solution_wrapper(
+        str_lambda: str, input: Tuple[int], calls: Dict[str, FunctionType]
+    ) -> Tuple[int]:
+        """
+        Оборачиваем исполнение функции в таймер и лимит памяти
+        для отслеживания превышений.
+
+        Возвращает:
+            (Tuple[int]):
+            Возвращает результаты выполнения функций filter + lambda
+        """
+        return tuple(filter(eval(str_lambda, calls), input))
+
+    safe_calls = {"sum": sum, "map": map, "str": str}
+    returned_output = solution_wrapper(solution, input, safe_calls)
+
+    assert returned_output == expected_output
diff --git a/tests/4.2/test_42_j.py b/tests/4.2/test_42_j.py
new file mode 100644
index 00000000..832bc953
--- /dev/null
+++ b/tests/4.2/test_42_j.py
@@ -0,0 +1,22 @@
+from typing import Callable, Dict, Tuple
+
+import pytest
+
+from tests.data.test_data_42 import j_test_data
+
+
+@pytest.mark.parametrize(
+    "text, replacements, expected_output, _",
+    j_test_data,
+    ids=[i[-1] for i in j_test_data],
+)
+def test_secret_replace(
+    decorated_function: Callable,
+    text: str,
+    replacements: Dict[str, Tuple[str]],
+    expected_output: Tuple[int],
+    _: str,
+) -> None:
+    returned_output = decorated_function(text, **replacements)
+
+    assert returned_output == expected_output
diff --git a/tests/constants.py b/tests/constants.py
index 76eb7dc6..b29bbd17 100644
--- a/tests/constants.py
+++ b/tests/constants.py
@@ -30,5 +30,7 @@
         "c": "gcd",
         "d": "month",
         "e": "to_string",
+        "f": "order",
+        "j": "secret_replace",
     },
 }
diff --git a/tests/data/test_data_42.py b/tests/data/test_data_42.py
index d4b869a8..8639d801 100644
--- a/tests/data/test_data_42.py
+++ b/tests/data/test_data_42.py
@@ -82,3 +82,234 @@
         "mix of data types",
     ),
 ]
+
+f_test_data = [
+    (
+        {"coffee": 1, "milk": 2, "cream": 3},  # global in_stock
+        (
+            (
+                "Эспрессо",
+                "Капучино",
+                "Макиато",
+                "Кофе по-венски",
+                "Латте Макиато",
+                "Кон Панна",
+            ),
+        )
+        * 2,
+        ("Эспрессо", "К сожалению, не можем предложить Вам напиток"),
+        "first open test",
+    ),
+    (
+        {"coffee": 4, "milk": 4, "cream": 0},
+        (("Капучино", "Макиато", "Эспрессо"),) * 3,
+        ("Капучино", "Макиато", "Эспрессо"),
+        "second open test",
+    ),
+    (
+        {"coffee": 1, "milk": 0, "cream": 1},
+        (
+            (
+                "Капучино",
+                "Макиато",
+                "Кофе по-венски",
+                "Латте Макиато",
+                "Кон Панна",
+            ),
+        ),
+        ("Кон Панна",),
+        "the last recipie",
+    ),
+    (
+        {"coffee": 7, "milk": 6, "cream": 4},
+        (
+            (
+                "Эспрессо",
+                "Капучино",
+                "Макиато",
+                "Кофе по-венски",
+                "Латте Макиато",
+                "Кон Панна",
+            ),
+            (
+                "Капучино",
+                "Макиато",
+                "Кофе по-венски",
+                "Латте Макиато",
+                "Кон Панна",
+            ),
+            (
+                "Макиато",
+                "Кофе по-венски",
+                "Латте Макиато",
+                "Кон Панна",
+            ),
+            (
+                "Кофе по-венски",
+                "Латте Макиато",
+                "Кон Панна",
+            ),
+            (
+                "Латте Макиато",
+                "Кон Панна",
+                "Макиато",
+                "Кофе по-венски",
+            ),
+            (
+                "Кон Панна",
+                "Макиато",
+                "Кофе по-венски",
+            ),
+            (
+                "Эспрессо",
+                "Макиато",
+                "Кофе по-венски",
+            ),
+        ),
+        (
+            "Эспрессо",
+            "Капучино",
+            "Макиато",
+            "Кофе по-венски",
+            "Латте Макиато",
+            "Кон Панна",
+            "К сожалению, не можем предложить Вам напиток",
+        ),
+        "zero ingredients in_stock left",
+    ),
+]
+
+g_test_data = [
+    (
+        ((1, 2, 3, 4, 5, 6), (1, 2)),
+        (((9, 12), (3.0, 4.0)), ((10, 14), (2.5, 3.5))),
+        "first open test",
+    ),
+    (
+        ((3.5, 2.14, 45.2, 37.99), (5.2, 7.3), (1.23, 4.56, 3.14, 2.71, 0, 0)),
+        (
+            ((48.7, 40.13), (24.35, 20.07)),
+            ((53.9, 47.43), (17.97, 15.81)),
+            ((58.27, 54.7), (9.71, 9.12)),
+        ),
+        "second open test",
+    ),
+    (
+        ((1.0, 2.0, 3.0, 4.0, 5.0, 6.0), (1, 2, 3, 4, 5, 6)),
+        (
+            ((9.0, 12.0), (3.0, 4.0)),
+            ((18.0, 24.0), (3.0, 4.0)),
+        ),
+        "same results",
+    ),
+    (
+        ((0.1, 0.2, 0.3, 0.4, 0.5, 0.6), (0.6, 0.5, 0.4, 0.3, 0.2, 0.1)),
+        (
+            ((0.9, 1.2), (0.3, 0.4)),
+            ((2.1, 2.1), (0.35, 0.35)),
+        ),
+        "small results",
+    ),
+    (
+        (
+            (10**4, 10**5, 10**6, 10**7, 10**8, 21 + 10**9),
+            (21**4, 112**5, 89**6, 15**7, 67**8, 27**9),
+        ),
+        (
+            ((101010000, 1010100021), (33670000.0, 336700007.0)),
+            (
+                (406564760052083, 7644401861215),
+                (67760793342013.84, 1274066976869.17),
+            ),
+        ),
+        "big numbers",
+    ),
+]
+
+h_test_data = [
+    ("мама мыла раму", ["мама", "мыла", "раму"], "first open test"),
+    (
+        "Яндекс использует Python во многих проектах",
+        ["во", "Python", "многих", "Яндекс", "проектах", "использует"],
+        "second open test",
+    ),
+    ("abc aab aaa", ["aaa", "aab", "abc"], "second key test"),
+    ("aBc Aab AAA", ["AAA", "Aab", "aBc"], "sEcOnD key test"),
+    ("abc", ["abc"], "one word"),
+    ("6 5 4 3 2 1 10", ["1", "2", "3", "4", "5", "6", "10"], "digits"),
+]
+
+i_test_data = [
+    ((1, 2, 3, 4, 5), (2, 4), "first open test"),
+    ((32, 64, 128, 256, 512), (64, 512), "second open test"),
+    ((10**12 + 1,), (10**12 + 1,), "big number"),
+    ((0, 0, 0, 0, 0), (0, 0, 0, 0, 0), "zeroes"),
+    (
+        tuple(range(1, 101, 2)),
+        (
+            11,
+            13,
+            15,
+            17,
+            19,
+            31,
+            33,
+            35,
+            37,
+            39,
+            51,
+            53,
+            55,
+            57,
+            59,
+            71,
+            73,
+            75,
+            77,
+            79,
+            91,
+            93,
+            95,
+            97,
+            99,
+        ),
+        "many odd numbers",
+    ),
+]
+
+j_test_data = [
+    (
+        "Hello, world!",
+        {"l": ("hi", "y"), "o": ("123", "z")},
+        "Hehiy123, wzrhid!",
+        "first open test",
+    ),
+    (
+        "ABRA-KADABRA",
+        {
+            "A": ("Z", "1", "!"),
+            "B": ("3",),
+            "R": ("X", "7"),
+            "K": ("G", "H"),
+            "D": ("0", "2"),
+        },
+        "Z3X1-G!0Z371",
+        "second open test",
+    ),
+    ("AAAAAAAAA", {"A": ("b", "1")}, "b1b1b1b1b", "same letter"),
+    ("", {}, "", "empty string"),
+    ("a", {"a": ("b",)}, "b", "one letter"),
+    ("a", {"a": (" ",)}, " ", "one letter to whitespace"),
+    (
+        "Ключевой секрет\n\n"
+        "Вася любит секреты и шифрование. "
+        "Он часто пользуется шифром на основе замен и просит разработать вас функцию, "
+        "которая позволит ему быстро шифровать сообщения.",
+        {"\n": ("\t", "\n\n"), "и": ("", "й"), "е": ("!", "?", "_")},
+        "Ключ!вой с?кр_т\t\n\n"
+        "Вася любт с!кр?ты й шфрованй_. "
+        "Он часто пользу!тся шфром на основ? зам_н й прост разработать вас функцйю, "
+        "которая позволт !му быстро шйфровать сообщ?ня.",
+        "weird signs",
+    ),
+]
diff --git a/tests/data/test_data_utils.py b/tests/data/test_data_utils.py
index 1518ddec..18844847 100644
--- a/tests/data/test_data_utils.py
+++ b/tests/data/test_data_utils.py
@@ -11,7 +11,7 @@
 time_limit_data_fail = [
     (None, "fails w/o arg"),
     (1.3, "fails with 130 ms"),
-    (1.001, "fails with 1001 ns"),
+    (1.01, "fails with 101 ms"),
 ]
 
 generate_error_msg_data = [