diff --git a/conditions/lesson04/tasks.md b/conditions/lesson04/tasks.md new file mode 100644 index 00000000..1858a622 --- /dev/null +++ b/conditions/lesson04/tasks.md @@ -0,0 +1,161 @@ +# Задача 1: Ты в прогрессируешь? +**Условие:** +Дан список целых чисел, который может быть не отсортирован. Требуется определить, можно ли расположить элементы этого списка в таком порядке, чтобы они образовывали арифметическую прогрессию. Арифметическая прогрессия — это последовательность чисел, в которой разность между любыми двумя соседними элементами постоянна. Например, последовательности `[1, 3, 5, 7]` и `[10, 5, 0, -5]` являются арифметическими прогрессиями, а `[1, 2, 4]` — нет. + +Допишите код в файле [task1](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson04/task1.py). + +**Входные данные:** +- `lst` — список целых чисел: + - Длина списка от 0 до 1000; + - Значения элементов от -10⁶ до 10⁶; + - Список может содержать дубликаты и быть неотсортированным. + +**Выходные данные:** +- `True`, если элементы списка являются элементами арифметической прогрессии, иначе `False`. + +**Примеры:** + +| Входные данные | Выходные данные | +|------------------|------------------| +| `[3, 1, 5, 7]` | `True` | +| `[1, 2, 4]` | `False` | + +# Задача 2: Почему в сутках не 25 часов? +**Условие:** +Первокурсник Азат пытался выделить время на подготовку к трём (на самом деле — семи) дедлайнам на этой неделе. Он записал все временные интервалы, когда *теоретически* мог бы учиться: например, `[9, 12]` — «проснулся и сразу в бой», `[11, 14]` — «решил продолжить, пока не ушёл обедать». Но оказалось, что некоторые интервалы пересекаются или даже сливаются в один непрерывный марафон отчаяния. + +Теперь Азату нужно понять, сколько **реальных блоков времени** у него есть — то есть объединить все пересекающиеся или касающиеся интервалы в непрерывные отрезки. Помогите ему не запутаться в собственном расписании! + +Дан список отрезков на числовой прямой. Каждый отрезок задаётся парой целых чисел — его левой и правой границей (включительно). Требуется объединить все пересекающиеся или касающиеся отрезки и вернуть список непересекающихся отрезков, покрывающих ту же область. + +Допишите код в файле [task2](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson04/task2.py). + +**Входные данные:** +- `intervals` — список списков целых чисел: + - Каждый вложенный список содержит ровно два элемента: `[left, right]`; + - Гарантируется, что `left <= right` для каждого отрезка; + - Длина списка от 0 до 1000; + - Значения границ: `0 ≤ left, right ≤ 10⁶`. + +**Выходные данные:** +- Список списков целых чисел: + - Каждый элемент — объединённый отрезок вида `[left, right]`; + - Отрезки в результате не пересекаются и не касаются друг друга; + - Список отсортирован по возрастанию левых границ. + +**Примеры:** + +| Входные данные | Выходные данные | +|-------------------------------------------------|-------------------------------------| +| `[[10, 13], [1, 3], [2, 6], [8, 10], [15, 18]]` | `[[1, 6], [8, 13], [15, 18]]` | +| `[[1, 4], [4, 5]]` | `[[1, 5]]` | + + +# Задача 3: Особенный +**Условие:** +Дан массив целых чисел, в котором все числа ведут себя прилично — встречаются ровно два раза, как положено в обществе. Но одно число возомнило себя «уникальным снежинкой» и появилось всего один раз, будто оно может решать что-то. + +Ваша задача — найти этого самопровозглашённого индивидуалиста, используя минимально возможную память и ровно один проход по данным. Решение должно работать за O(N) по времени и O(1) по памяти — потому что даже уникальность не отменяет законы эффективности. + +Допишите код в файле [task3](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson04/task3.py). + +**Входные данные:** +- `nums` — список целых чисел: + - Длина списка: нечётная от 1 до 1001; + - Все числа, кроме одного, встречаются ровно два раза; + - Значения элементов от -10⁶ до 10⁶`. + +**Выходные данные:** +- Целое число, которое встречается в списке один раз. + +**Примеры:** + +| Входные данные | Выходные данные | +|----------------------|-----------------| +| `[2, 2, 1]` | `1` | +| `[4, 1, 2, 1, 2]` | `4` | + + +# Задача 4: Очистка от мусора +**Условие:** +Вы работаете с «грязными» данными: в списке чисел случайным образом оказались нули — будто кто-то нажал «сброс» в самый неподходящий момент. Чтобы система не сломалась, нужно аккуратно убрать все нули в конец, **не меняя порядок остальных чисел** и **не создавая новый список** (память на сервере — дороже кофе в пятницу). + +После перестановки вам нужно сообщить, **с какого индекса начинается «нулевая зона»** — это поможет другим модулям знать, где заканчиваются полезные данные. + +Допишите код в файле [task4](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson04/task4.py). + +**Входные данные:** +- `nums` — список целых чисел: + - Длина от 0 до 1000; + - Значения элементов от -10⁶ до 10⁶. + +**Выходные данные:** +- Функция **изменяет исходный список на месте**; +- Возвращает **целое число** — индекс первого нуля после перестановки; + - Если нулей нет, возвращается длину `nums`; + - Если все элементы — нули, возвращается `0`. + +**Примеры:** + +| Входные данные | Список после обработки | Выходные данные | +|----------------------|------------------------|-----------------| +| `[0, 1, 0, 3, 12]` | `[1, 3, 12, 0, 0]` | `3` | +| `[1, 0, 2, 0, 3, 0]` | `[1, 2, 3, 0, 0, 0]` | `3` | + + +# Задача 5: Трудяга +**Условие:** +Вы анализируете логи работы серверов в дата-центре. Каждый сервер представлен строкой из нулей и единиц: +- **0** означает «простаивал», +- **1** — «обрабатывал запросы». + +Из-за особенностей системы, в каждой строке сначала идут **нули**, а затем — **сплошной блок единиц** (без разрывов). Ваша задача — найти **сервер, который проработал дольше всех** (то есть строку с наибольшим количеством единиц). + +И чтобы не тратить драгоценное время на перебор всей матрицы, нужно сделать это **максимально эффективно** — за **O(N + M)**, где `N` — число серверов, `M` — число временных интервалов. + +Допишите код в файле [task5](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson04/task5.py). + +**Входные данные:** +- `matrix` — список списков целых чисел: + - Все вложенные списки имеют одинаковую длину `M`; + - Элементы — только `0` или `1`; + - В каждой строке: сначала идут нули (возможно, 0 штук), затем — единицы (возможно, 0 штук), **без чередования**; + - Ограничения: `0 ≤ N ≤ 10000`, `0 ≤ M ≤ 10000`. + +**Выходные данные:** +- Индекс строки (целое число от `0` до `N-1`), в которой **наибольшее количество единиц** + - Если таких строк несколько — верните с минимальным индесом; + - Если матрица пуста — верните `0`. + +**Примеры:** + +| Входные данные | Выходные данные | +|-------------------------------------------------------------|-----------------| +| `[[0, 0, 1, 1], [0, 1, 1, 1], [1, 1, 1, 1], [0, 0, 0, 1]]` | `2` | +| `[[0, 0, 0], [0, 0, 0], [0, 0, 0]]` | `0` | + +# Задача 6: Не зацикливайся +**Условие:** +Вы получили странный массив, в котором каждый элемент — это **указатель на следующую позицию**: значение `arr[i]` говорит, куда «прыгать» из индекса `i`. Гарантируется, что **все элементы вовлечены в циклы** — никто не ведёт в никуда, и нет «хвостов», ведущих к циклу. Даже если элемент указывает сам на себя — это тоже цикл (длины 1). + +Ваша задача — определить, **сколько всего независимых циклов** спрятано в этом массиве. + +Допишите код в файле [task6](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson04/task6.py). + +**Входные данные:** +- `arr` — список целых чисел: + - Длина от 0 до 1000; + - Каждое значение — корректный индекс: `0 ≤ arr[i] < len(arr)`; + - Гарантируется, что каждый элемент принадлежит ровно одному циклу. + +**Выходные данные:** +- Целое число — количество циклов в массиве. + +**Примеры:** + +| Входные данные | Выходные данные | +|--------------------|-----------------| +| `[1, 0]` | `1` | +| `[0, 1, 2]` | `3` | +| `[1, 0, 3, 2]` | `2` | + diff --git a/solutions/lesson04/task1.py b/solutions/lesson04/task1.py new file mode 100644 index 00000000..47384423 --- /dev/null +++ b/solutions/lesson04/task1.py @@ -0,0 +1,3 @@ +def is_arithmetic_progression(lst: list[list[int]]) -> bool: + # ваш код + return False \ No newline at end of file diff --git a/solutions/lesson04/task2.py b/solutions/lesson04/task2.py new file mode 100644 index 00000000..4591d0a3 --- /dev/null +++ b/solutions/lesson04/task2.py @@ -0,0 +1,3 @@ +def merge_intervals(intervals: list[list[int, int]]) -> list[list[int, int]]: + # ваш код + return [[0,0]] \ No newline at end of file diff --git a/solutions/lesson04/task3.py b/solutions/lesson04/task3.py new file mode 100644 index 00000000..7253f6cb --- /dev/null +++ b/solutions/lesson04/task3.py @@ -0,0 +1,3 @@ +def find_single_number(nums: list[int]) -> int: + # ваш код + return 0 diff --git a/solutions/lesson04/task4.py b/solutions/lesson04/task4.py new file mode 100644 index 00000000..b21bc5a3 --- /dev/null +++ b/solutions/lesson04/task4.py @@ -0,0 +1,3 @@ +def move_zeros_to_end(nums: list[int]) -> list[int]: + # ваш код + return 0 \ No newline at end of file diff --git a/solutions/lesson04/task5.py b/solutions/lesson04/task5.py new file mode 100644 index 00000000..02d7742b --- /dev/null +++ b/solutions/lesson04/task5.py @@ -0,0 +1,3 @@ +def find_row_with_most_ones(matrix: list[list[int]]) -> int: + # ваш код + return 0 \ No newline at end of file diff --git a/solutions/lesson04/task6.py b/solutions/lesson04/task6.py new file mode 100644 index 00000000..16df27ca --- /dev/null +++ b/solutions/lesson04/task6.py @@ -0,0 +1,3 @@ +def count_cycles(arr: list[int]) -> int: + # ваш код + return 0 \ No newline at end of file diff --git a/tests/test_lesson04_tasks.py b/tests/test_lesson04_tasks.py new file mode 100644 index 00000000..c8278329 --- /dev/null +++ b/tests/test_lesson04_tasks.py @@ -0,0 +1,159 @@ +import pytest +import random + +from solutions.lesson04.task1 import is_arithmetic_progression +from solutions.lesson04.task2 import merge_intervals +from solutions.lesson04.task3 import find_single_number +from solutions.lesson04.task4 import move_zeros_to_end +from solutions.lesson04.task5 import find_row_with_most_ones +from solutions.lesson04.task6 import count_cycles + +@pytest.mark.parametrize("lst, expected", [ + pytest.param([], True, id="empty_list"), + pytest.param([5], True, id="single_element"), + pytest.param([1, 3], True, id="two_elements"), + pytest.param([3, 1], True, id="two_elements_unsorted"), + pytest.param([1, 3, 5, 7], True, id="already_sorted_ap"), + pytest.param([3, 1, 5, 7], True, id="unsorted_ap"), + pytest.param([1, 2, 4], False, id="not_ap"), + pytest.param([10, 5, 0, -5], True, id="negative_difference"), + pytest.param([1, 1, 1, 1], True, id="constant_sequence"), + pytest.param([1, 2, 3, 5], False, id="almost_ap_but_not"), + pytest.param([0, 0, 1], False, id="two_same_one_different"), + pytest.param([10**5 + i*10**2 for i in range(1000)], True, id="long_list_true"), + pytest.param([10**5 + i*10**2 for i in range(999)] + [1], False, id="long_list_false"), +]) +def test_is_arithmetic_progression_parametrized(lst, expected): + if len(lst) > 500: + random.shuffle(lst) + assert is_arithmetic_progression(lst) == expected + + +@pytest.mark.parametrize("intervals, expected", [ + pytest.param([], [], id="empty"), + pytest.param([[1, 3]], [[1, 3]], id="single_interval"), + pytest.param([[10, 13], [1, 3], [2, 6], [8, 10], [15, 18]], [[1, 6], [8, 13], [15, 18]], id="classic_merge"), + pytest.param([[1, 4], [4, 5]], [[1, 5]], id="touching_intervals"), + pytest.param([[1, 4], [2, 3]], [[1, 4]], id="nested_interval"), + pytest.param([[5, 7], [1, 3], [15, 20], [0, 0], [2, 4], [6, 10], [0, 2]], [[0, 4], [5, 10], [15, 20]], id="unsorted_input"), + pytest.param([[1, 2], [3, 4], [5, 6]], [[1, 2], [3, 4], [5, 6]], id="no_overlap"), + pytest.param([[1, 10], [2, 3], [4, 5], [6, 7]], [[1, 10]], id="all_merged"), +]) +def test_merge_intervals(intervals, expected): + assert merge_intervals(intervals) == expected + +@pytest.mark.parametrize("nums, expected", [ + pytest.param([2, 2, 1], 1, id="simple_case"), + pytest.param([4, 1, 2, 1, 2], 4, id="middle_single"), + pytest.param([1], 1, id="single_element"), + pytest.param([100, 200, 300, 200, 100], 300, id="large_numbers"), + pytest.param([0, 1, 0], 1, id="with_zero"), + pytest.param([7, 8, 9, 8, 7], 9, id="unsorted"), + pytest.param([i + 10**5 for i in range(500)] + [i + 10**5 for i in range(500)] + [69], 69, id="long_list"), +]) +def test_find_single_number(nums, expected): + assert find_single_number(nums) == expected + +@pytest.mark.parametrize("input_list, expected_list, expected_index", [ + pytest.param([0, 1, 0, 3, 12], [1, 3, 12, 0, 0], 3, id="basic"), + pytest.param([0, 0, 1], [1, 0, 0], 1, id="zeros_first"), + pytest.param([1, 2, 3], [1, 2, 3], 3, id="no_zeros"), + pytest.param([0, 0, 0], [0, 0, 0], 0, id="all_zeros"), + pytest.param([1, 0, 2, 0, 3, 0], [1, 2, 3, 0, 0, 0], 3, id="interleaved"), + pytest.param([], [], 0, id="empty"), + pytest.param([0], [0], 0, id="single_zero"), + pytest.param([42], [42], 1, id="single_nonzero"), +]) +def test_move_zeros_to_end_parametrized(input_list, expected_list, expected_index): + arr = input_list[:] + result_index = move_zeros_to_end(arr) + assert arr == expected_list + assert result_index == expected_index + + +@pytest.mark.parametrize("matrix, expected_row", [ + pytest.param( + [[0, 0, 1, 1], + [0, 1, 1, 1], + [0, 0, 0, 1], + [1, 1, 1, 1], + [0, 1, 1, 1]], + 3, + id="classic" + ), + pytest.param( + [[0, 0, 0], + [0, 0, 0], + [0, 0, 0]], + 0, + id="all_zeros" + ), + pytest.param( + [[1, 1, 1], + [1, 1, 1], + [1, 1, 1]], + 0, + id="all_ones_first" + ), + pytest.param( + [[0, 1], + [1, 1]], + 1, + id="two_rows" + ), + pytest.param( + [[0]], + 0, + id="single_zero" + ), + pytest.param( + [[1]], + 0, + id="single_one" + ), + pytest.param( + [], + 0, + id="empty_matrix" + ), + pytest.param( + [[0, 0, 1], + [0, 1, 1], + [0, 1, 1]], + 1, + id="tie" + ), +]) +def test_find_row_with_most_ones(matrix, expected_row): + assert find_row_with_most_ones(matrix) == expected_row + + +def test_find_row_with_most_ones_big_data(): + size = 10000 + matrix = [[0]*size for i in range(size)] + matrix[size-1][size-1] = 1 + + for i in range(50): + assert find_row_with_most_ones(matrix) == 9999 + + size = 10000 + matrix = [[1]*size for i in range(size)] + matrix[0][0] = 0 + + for i in range(50): + assert find_row_with_most_ones(matrix) == 1 + + +@pytest.mark.parametrize("input_arr, expected", [ + pytest.param([0], 1, id="self_loop"), + pytest.param([1, 0], 1, id="two_cycle"), + pytest.param([1, 2, 0], 1, id="three_cycle"), + pytest.param([0, 1, 2], 3, id="three_self_loops"), + pytest.param([1, 0, 3, 2], 2, id="two_2_cycles"), + pytest.param([2, 0, 1, 4, 3], 2, id="mixed_cycles"), + pytest.param([10, 6, 2, 9, 4, 0, 3, 8, 7, 1, 5], 5, id="mixed_cycles"), + pytest.param([], 0, id="empty"), +]) +def test_count_cycles(input_arr, expected): + arr = input_arr[:] + assert count_cycles(arr) == expected