From 2213414666e48ae9cc65495e50aa0c15423de925 Mon Sep 17 00:00:00 2001 From: kiritonator Date: Fri, 20 Mar 2026 23:30:07 +0300 Subject: [PATCH 1/4] lesson 05 done --- .idea/.gitignore | 5 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/python_mipt_dafe_tasks.iml | 15 + .idea/vcs.xml | 6 + .../sem01/tests/test_lesson02_tasks.py | 16 +- .../sem01/tests/test_lesson04_tasks.py | 215 +++++++------- .../sem01/tests/test_lesson05_tasks.py | 269 ++++++++++-------- .../sem01/tests/test_lesson06_tasks.py | 196 +++++++------ .../sem01/tests/test_lesson08_tasks.py | 13 +- .../sem01/tests/test_lesson11_tasks.py | 8 +- .../sem01/tests_hw/test_hw1_tasks.py | 54 ++-- .../sem02/tests/task4/test_lesson04_tasks.py | 8 +- .../sem02/tests/task5/test_lesson05_tasks.py | 8 +- homeworks/sem01/hw1/backoff.py | 2 - solutions/sem01/lesson02/task3.py | 2 +- solutions/sem01/lesson03/task1.py | 2 +- solutions/sem01/lesson03/task2.py | 2 +- solutions/sem01/lesson04/task1.py | 2 +- solutions/sem01/lesson04/task2.py | 2 +- solutions/sem01/lesson04/task4.py | 2 +- solutions/sem01/lesson04/task5.py | 2 +- solutions/sem01/lesson04/task6.py | 4 +- solutions/sem01/lesson05/task1.py | 2 +- solutions/sem01/lesson05/task2.py | 2 +- solutions/sem01/lesson05/task4.py | 2 +- solutions/sem01/lesson05/task5.py | 4 +- solutions/sem01/lesson05/task6.py | 2 +- solutions/sem01/lesson06/task1.py | 2 +- solutions/sem01/lesson06/task2.py | 2 +- solutions/sem01/lesson06/task3.py | 1 - solutions/sem01/lesson06/task4.py | 2 +- solutions/sem01/lesson08/task1.py | 3 +- solutions/sem01/lesson08/task2.py | 8 +- solutions/sem01/lesson12/task3.py | 3 - solutions/sem02/lesson05/task1.py | 12 +- solutions/sem02/lesson05/task2.py | 16 +- solutions/sem02/lesson05/task3.py | 14 +- 39 files changed, 512 insertions(+), 417 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/python_mipt_dafe_tasks.iml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..b58b603fe --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 000000000..105ce2da2 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..590a59e60 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..c2147b6e4 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/python_mipt_dafe_tasks.iml b/.idea/python_mipt_dafe_tasks.iml new file mode 100644 index 000000000..98e94e406 --- /dev/null +++ b/.idea/python_mipt_dafe_tasks.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/deprecated_tests/sem01/tests/test_lesson02_tasks.py b/deprecated_tests/sem01/tests/test_lesson02_tasks.py index f11403f4d..b7febe4ba 100644 --- a/deprecated_tests/sem01/tests/test_lesson02_tasks.py +++ b/deprecated_tests/sem01/tests/test_lesson02_tasks.py @@ -10,7 +10,7 @@ @pytest.mark.parametrize( - "num, result_expected", + "num, result_expected", ( pytest.param( 0, @@ -44,7 +44,7 @@ def test_get_factorial(num: int, result_expected: int) -> None: @pytest.mark.parametrize( - "num, result_expected", + "num, result_expected", ( pytest.param( 0, @@ -83,7 +83,7 @@ def test_get_doubled_factorial(num: int, result_expected: int) -> None: @pytest.mark.parametrize( - "num, result_expected", + "num, result_expected", ( pytest.param( 1, @@ -125,7 +125,7 @@ def test_get_amount_of_ways_to_climb( @pytest.mark.parametrize( - "num, result_expected", + "num, result_expected", ( pytest.param( 1, @@ -162,7 +162,7 @@ def test_get_multiplications_amount( @pytest.mark.parametrize( - "num1, num2, result_expected", + "num1, num2, result_expected", ( pytest.param( 1, @@ -229,7 +229,7 @@ def test_get_gcd( @pytest.mark.parametrize( - "num, result_expected", + "num, result_expected", ( pytest.param( 1, @@ -273,10 +273,10 @@ def test_get_sum_of_prime_divisors(num: int, result_expected: int) -> None: @pytest.mark.parametrize( - "num, result_expected", + "num, result_expected", ( pytest.param( - -10**10, + -(10**10), False, id="negative-ten-billion", ), diff --git a/deprecated_tests/sem01/tests/test_lesson04_tasks.py b/deprecated_tests/sem01/tests/test_lesson04_tasks.py index 4110bdcc0..c98b6f22a 100644 --- a/deprecated_tests/sem01/tests/test_lesson04_tasks.py +++ b/deprecated_tests/sem01/tests/test_lesson04_tasks.py @@ -1,6 +1,7 @@ -import pytest import random +import pytest + from solutions.sem01.lesson04.task1 import is_arithmetic_progression from solutions.sem01.lesson04.task2 import merge_intervals from solutions.sem01.lesson04.task3 import find_single_number @@ -8,62 +9,89 @@ from solutions.sem01.lesson04.task5 import find_row_with_most_ones from solutions.sem01.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"), -]) + +@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"), -]) +@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"), -]) + +@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"), -]) + +@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) @@ -71,89 +99,54 @@ def test_move_zeros_to_end_parametrized(input_list, expected_list, expected_inde 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" - ), -]) +@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 + 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 = [[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"), -]) +@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 diff --git a/deprecated_tests/sem01/tests/test_lesson05_tasks.py b/deprecated_tests/sem01/tests/test_lesson05_tasks.py index 72ad6bc8f..a43a2a456 100644 --- a/deprecated_tests/sem01/tests/test_lesson05_tasks.py +++ b/deprecated_tests/sem01/tests/test_lesson05_tasks.py @@ -1,4 +1,4 @@ -import pytest +import pytest from solutions.sem01.lesson05.task1 import is_palindrome from solutions.sem01.lesson05.task2 import are_anagrams @@ -7,145 +7,166 @@ from solutions.sem01.lesson05.task5 import reg_validator from solutions.sem01.lesson05.task6 import simplify_path -@pytest.mark.parametrize("s, expected", [ - pytest.param("", True, id="empty_string"), - pytest.param("a", True, id="single_char"), - pytest.param("aa", True, id="two_same"), - pytest.param("ab", False, id="two_different"), - pytest.param("aba", True, id="odd_palindrome"), - pytest.param("abba", True, id="even_palindrome"), - pytest.param("abcba", True, id="long_odd_palindrome"), - pytest.param("abccba", True, id="long_even_palindrome"), - pytest.param("abc", False, id="not_palindrome"), - pytest.param("Aa", True, id="case_sensitive_mismatch"), - pytest.param("Racecar", True, id="real_word_case_sensitive"), - pytest.param("aA", True, id="reverse_case"), - pytest.param("abcdefedcba", True, id="long_true"), - pytest.param("abcdefedcbx", False, id="long_false"), -]) + +@pytest.mark.parametrize( + "s, expected", + [ + pytest.param("", True, id="empty_string"), + pytest.param("a", True, id="single_char"), + pytest.param("aa", True, id="two_same"), + pytest.param("ab", False, id="two_different"), + pytest.param("aba", True, id="odd_palindrome"), + pytest.param("abba", True, id="even_palindrome"), + pytest.param("abcba", True, id="long_odd_palindrome"), + pytest.param("abccba", True, id="long_even_palindrome"), + pytest.param("abc", False, id="not_palindrome"), + pytest.param("Aa", True, id="case_sensitive_mismatch"), + pytest.param("Racecar", True, id="real_word_case_sensitive"), + pytest.param("aA", True, id="reverse_case"), + pytest.param("abcdefedcba", True, id="long_true"), + pytest.param("abcdefedcbx", False, id="long_false"), + ], +) def test_is_palindrome(s, expected): assert is_palindrome(s) == expected -@pytest.mark.parametrize("w1, w2, expected", [ - pytest.param("listen", "silent", True, id="classic_anagram"), - pytest.param("evil", "vile", True, id="another_anagram"), - pytest.param("a", "a", True, id="single_char_same"), - pytest.param("A", "A", True, id="single_upper_same"), - pytest.param("A", "a", False, id="case_sensitive_diff"), - pytest.param("Listen", "Silent", False, id="mixed_case_not_anagram"), - pytest.param("Aa", "aA", True, id="same_chars_permuted"), - pytest.param("Ab", "ab", False, id="one_letter_case_diff"), - pytest.param("abc", "cba", True, id="permuted_same_case"), - pytest.param("abc", "Cba", False, id="case_breaks_anagram"), - pytest.param("aabbcc", "abcabc", True, id="repeated_letters"), - pytest.param("aabbcc", "aabbcd", False, id="extra_different_char"), -]) +@pytest.mark.parametrize( + "w1, w2, expected", + [ + pytest.param("listen", "silent", True, id="classic_anagram"), + pytest.param("evil", "vile", True, id="another_anagram"), + pytest.param("a", "a", True, id="single_char_same"), + pytest.param("A", "A", True, id="single_upper_same"), + pytest.param("A", "a", False, id="case_sensitive_diff"), + pytest.param("Listen", "Silent", False, id="mixed_case_not_anagram"), + pytest.param("Aa", "aA", True, id="same_chars_permuted"), + pytest.param("Ab", "ab", False, id="one_letter_case_diff"), + pytest.param("abc", "cba", True, id="permuted_same_case"), + pytest.param("abc", "Cba", False, id="case_breaks_anagram"), + pytest.param("aabbcc", "abcabc", True, id="repeated_letters"), + pytest.param("aabbcc", "aabbcd", False, id="extra_different_char"), + ], +) def test_are_anagrams_linear(w1, w2, expected): assert are_anagrams(w1, w2) == expected -@pytest.mark.parametrize("s, expected", [ - pytest.param("!!!", True, id="only_exclamations"), - pytest.param("...?", True, id="dots_and_question"), - pytest.param("", False, id="empty_string"), - pytest.param("a", False, id="letter"), - pytest.param("1", False, id="digit"), - pytest.param(" ! ", False, id="space_inside"), - pytest.param("!?.", True, id="symbols_only"), - pytest.param("!a!", False, id="letter_in_middle"), - pytest.param(" ", False, id="only_space"), - pytest.param(".,;", True, id="commas_dots_semicolons"), - pytest.param("", False, id="commas_dots_semicolons"), -]) +@pytest.mark.parametrize( + "s, expected", + [ + pytest.param("!!!", True, id="only_exclamations"), + pytest.param("...?", True, id="dots_and_question"), + pytest.param("", False, id="empty_string"), + pytest.param("a", False, id="letter"), + pytest.param("1", False, id="digit"), + pytest.param(" ! ", False, id="space_inside"), + pytest.param("!?.", True, id="symbols_only"), + pytest.param("!a!", False, id="letter_in_middle"), + pytest.param(" ", False, id="only_space"), + pytest.param(".,;", True, id="commas_dots_semicolons"), + pytest.param("", False, id="commas_dots_semicolons"), + ], +) def test_is_only_punctuation(s, expected): assert is_punctuation(s) == expected -@pytest.mark.parametrize("compressed, expected", [ - pytest.param("AbcD*4 ef GhI*2", "AbcDAbcDAbcDAbcDefGhIGhI", id="example"), - pytest.param("a*3 b*2", "aaabb", id="simple_letters"), - pytest.param("Hello", "Hello", id="star_one"), - pytest.param("xyz", "xyz", id="no_compression"), - pytest.param("", "", id="empty_input"), - pytest.param("Test*2 Space", "TestTestSpace", id="mixed"), - pytest.param("a*10", "aaaaaaaaaa", id="ten_a"), - pytest.param("x y z", "xyz", id="three_plain"), - pytest.param("Word word", "Wordword", id="case_sensitive"), -]) + +@pytest.mark.parametrize( + "compressed, expected", + [ + pytest.param("AbcD*4 ef GhI*2", "AbcDAbcDAbcDAbcDefGhIGhI", id="example"), + pytest.param("a*3 b*2", "aaabb", id="simple_letters"), + pytest.param("Hello", "Hello", id="star_one"), + pytest.param("xyz", "xyz", id="no_compression"), + pytest.param("", "", id="empty_input"), + pytest.param("Test*2 Space", "TestTestSpace", id="mixed"), + pytest.param("a*10", "aaaaaaaaaa", id="ten_a"), + pytest.param("x y z", "xyz", id="three_plain"), + pytest.param("Word word", "Wordword", id="case_sensitive"), + ], +) def test_decompress(compressed, expected): assert unzip(compressed) == expected -@pytest.mark.parametrize("regexp, s, expected", [ - pytest.param("d", "123", True, id="d_valid_number"), - pytest.param("d", "0", True, id="d_zero"), - pytest.param("d", "abc", False, id="d_letters_instead_of_digits"), - pytest.param("d", "", False, id="d_empty_string"), - pytest.param("w", "hello", True, id="w_lowercase_word"), - pytest.param("w", "HelloWorld", True, id="w_mixed_case_word"), - pytest.param("w", "hello123", False, id="w_word_with_digits"), - pytest.param("w", "", False, id="w_empty_string"), - pytest.param("s", "abc123", True, id="s_alphanum"), - pytest.param("s", "ABC99", True, id="s_uppercase_and_digits"), - pytest.param("s", "abc_123", False, id="s_contains_underscore"), - pytest.param("s", "", False, id="s_empty_string"), - pytest.param("d-d", "12-34", True, id="d_dash_d_valid"), - pytest.param("d-d", "12--34", False, id="d_dash_d_double_dash"), - pytest.param("d-d", "12-abc", False, id="d_dash_d_letters_after_dash"), - pytest.param("d-d", "1234", False, id="d_dash_d_missing_dash"), - pytest.param("w.w", "hi.there", True, id="w_dot_w_valid"), - pytest.param("w.w", "hi..there", False, id="w_dot_w_double_dot"), - pytest.param("w.w", "hi1.there", False, id="w_dot_w_digit_in_first_word"), - pytest.param("s.s", "h1i.th32ere", True, id="s_dot_s_valid"), - pytest.param("s.s", "hi4..t2here", False, id="s_dot_s_double_dot"), - pytest.param("d-dw", "12-45abc", True, id="example_valid"), - pytest.param("d-dw", "1-abs", False, id="example_second_part_not_digit"), - pytest.param("d-dw", "1-b123r", False, id="example_letter_after_dash"), - pytest.param("d-dw", "1--123vdg", False, id="example_double_dash"), - pytest.param("d-dw", "123-456XYZ", True, id="d-dw_all_caps"), - pytest.param("d-dw", "0-0a", True, id="d-dw_minimal_valid"), - pytest.param("d@d", "5@7", True, id="d_at_d_valid"), - pytest.param("d@d", "5@@7", False, id="d_at_d_double_at"), - pytest.param("w s", "hi 123", True, id="w_space_s_valid"), - pytest.param("w s", "hi123", False, id="w_space_s_missing_space"), - pytest.param("w s", "hi 123!", False, id="w_space_s_extra_char_in_s"), - pytest.param("", "", True, id="empty_regexp_empty_string"), - pytest.param("", "a", False, id="empty_regexp_non_empty_string"), - pytest.param("d", "", False, id="non_empty_regexp_empty_string"), - pytest.param("d!", "5!", True, id="d_exclam_valid"), - pytest.param("d!", "5", False, id="d_exclam_missing_exclam"), - pytest.param("d!", "5!!", False, id="d_exclam_extra_exclam"), - pytest.param("s", "a1", True, id="s_letter_digit"), - pytest.param("s", "1a", True, id="s_digit_letter"), - pytest.param("s", "a!1", False, id="s_contains_exclamation"), - pytest.param("d-w-s", "123-abc-XY1Z23", True, id="d_w_s_valid"), - pytest.param("d-w-s", "123-abc-XYZ_123", False, id="d_w_s_underscore_in_s"), -]) + +@pytest.mark.parametrize( + "regexp, s, expected", + [ + pytest.param("d", "123", True, id="d_valid_number"), + pytest.param("d", "0", True, id="d_zero"), + pytest.param("d", "abc", False, id="d_letters_instead_of_digits"), + pytest.param("d", "", False, id="d_empty_string"), + pytest.param("w", "hello", True, id="w_lowercase_word"), + pytest.param("w", "HelloWorld", True, id="w_mixed_case_word"), + pytest.param("w", "hello123", False, id="w_word_with_digits"), + pytest.param("w", "", False, id="w_empty_string"), + pytest.param("s", "abc123", True, id="s_alphanum"), + pytest.param("s", "ABC99", True, id="s_uppercase_and_digits"), + pytest.param("s", "abc_123", False, id="s_contains_underscore"), + pytest.param("s", "", False, id="s_empty_string"), + pytest.param("d-d", "12-34", True, id="d_dash_d_valid"), + pytest.param("d-d", "12--34", False, id="d_dash_d_double_dash"), + pytest.param("d-d", "12-abc", False, id="d_dash_d_letters_after_dash"), + pytest.param("d-d", "1234", False, id="d_dash_d_missing_dash"), + pytest.param("w.w", "hi.there", True, id="w_dot_w_valid"), + pytest.param("w.w", "hi..there", False, id="w_dot_w_double_dot"), + pytest.param("w.w", "hi1.there", False, id="w_dot_w_digit_in_first_word"), + pytest.param("s.s", "h1i.th32ere", True, id="s_dot_s_valid"), + pytest.param("s.s", "hi4..t2here", False, id="s_dot_s_double_dot"), + pytest.param("d-dw", "12-45abc", True, id="example_valid"), + pytest.param("d-dw", "1-abs", False, id="example_second_part_not_digit"), + pytest.param("d-dw", "1-b123r", False, id="example_letter_after_dash"), + pytest.param("d-dw", "1--123vdg", False, id="example_double_dash"), + pytest.param("d-dw", "123-456XYZ", True, id="d-dw_all_caps"), + pytest.param("d-dw", "0-0a", True, id="d-dw_minimal_valid"), + pytest.param("d@d", "5@7", True, id="d_at_d_valid"), + pytest.param("d@d", "5@@7", False, id="d_at_d_double_at"), + pytest.param("w s", "hi 123", True, id="w_space_s_valid"), + pytest.param("w s", "hi123", False, id="w_space_s_missing_space"), + pytest.param("w s", "hi 123!", False, id="w_space_s_extra_char_in_s"), + pytest.param("", "", True, id="empty_regexp_empty_string"), + pytest.param("", "a", False, id="empty_regexp_non_empty_string"), + pytest.param("d", "", False, id="non_empty_regexp_empty_string"), + pytest.param("d!", "5!", True, id="d_exclam_valid"), + pytest.param("d!", "5", False, id="d_exclam_missing_exclam"), + pytest.param("d!", "5!!", False, id="d_exclam_extra_exclam"), + pytest.param("s", "a1", True, id="s_letter_digit"), + pytest.param("s", "1a", True, id="s_digit_letter"), + pytest.param("s", "a!1", False, id="s_contains_exclamation"), + pytest.param("d-w-s", "123-abc-XY1Z23", True, id="d_w_s_valid"), + pytest.param("d-w-s", "123-abc-XYZ_123", False, id="d_w_s_underscore_in_s"), + ], +) def test_match_pattern(regexp, s, expected): assert reg_validator(regexp, s) == expected -@pytest.mark.parametrize("path, expected", [ - pytest.param("/home/", "/home", id="trailing_slash"), - pytest.param("/../", "", id="go_above_root"), - pytest.param("/home//foo/", "/home/foo", id="double_slash"), - pytest.param("/home/./foo/", "/home/foo", id="current_dir_dot"), - pytest.param("/./././", "/", id="only_dots_and_slashes"), - pytest.param("/a/./b/../../c/", "/c", id="complex_up_and_down"), - pytest.param("/a/b/c/../../../", "/", id="back_to_root"), - pytest.param("/", "/", id="root_only"), - pytest.param("/.", "/", id="root_with_dot"), - pytest.param("/..", "", id="root_with_double_dot"), - pytest.param("/...", "/...", id="triple_dot_as_name"), - pytest.param("/..a", "/..a", id="dot_dot_a_as_name"), - pytest.param("/a.b/c.d", "/a.b/c.d", id="names_with_dots"), - pytest.param("/a//b////c/d//././/..", "/a/b/c", id="messy_path"), - pytest.param("/a/./b/./c/./d", "/a/b/c/d", id="dots_everywhere"), - pytest.param("/a/./b/../../c/./d/", "/c/d", id="up_down_with_dots"), - pytest.param("/../foo", "", id="up_then_valid"), - pytest.param("/../../foo", "", id="multiple_up_then_valid"), - pytest.param("/../../../", "", id="three_up_from_root"), - pytest.param("/home/foo/./../../../", "", id="too_many_up"), - pytest.param("/_a.b/c__1/..", "/_a.b", id="names_with_underscores_and_dots"), -]) +@pytest.mark.parametrize( + "path, expected", + [ + pytest.param("/home/", "/home", id="trailing_slash"), + pytest.param("/../", "", id="go_above_root"), + pytest.param("/home//foo/", "/home/foo", id="double_slash"), + pytest.param("/home/./foo/", "/home/foo", id="current_dir_dot"), + pytest.param("/./././", "/", id="only_dots_and_slashes"), + pytest.param("/a/./b/../../c/", "/c", id="complex_up_and_down"), + pytest.param("/a/b/c/../../../", "/", id="back_to_root"), + pytest.param("/", "/", id="root_only"), + pytest.param("/.", "/", id="root_with_dot"), + pytest.param("/..", "", id="root_with_double_dot"), + pytest.param("/...", "/...", id="triple_dot_as_name"), + pytest.param("/..a", "/..a", id="dot_dot_a_as_name"), + pytest.param("/a.b/c.d", "/a.b/c.d", id="names_with_dots"), + pytest.param("/a//b////c/d//././/..", "/a/b/c", id="messy_path"), + pytest.param("/a/./b/./c/./d", "/a/b/c/d", id="dots_everywhere"), + pytest.param("/a/./b/../../c/./d/", "/c/d", id="up_down_with_dots"), + pytest.param("/../foo", "", id="up_then_valid"), + pytest.param("/../../foo", "", id="multiple_up_then_valid"), + pytest.param("/../../../", "", id="three_up_from_root"), + pytest.param("/home/foo/./../../../", "", id="too_many_up"), + pytest.param("/_a.b/c__1/..", "/_a.b", id="names_with_underscores_and_dots"), + ], +) def test_simplify_path(path, expected): - assert simplify_path(path) == expected \ No newline at end of file + assert simplify_path(path) == expected diff --git a/deprecated_tests/sem01/tests/test_lesson06_tasks.py b/deprecated_tests/sem01/tests/test_lesson06_tasks.py index 707d6609f..e20a8dd81 100644 --- a/deprecated_tests/sem01/tests/test_lesson06_tasks.py +++ b/deprecated_tests/sem01/tests/test_lesson06_tasks.py @@ -1,4 +1,4 @@ -import pytest +import pytest from solutions.sem01.lesson06.task1 import int_to_roman from solutions.sem01.lesson06.task2 import get_len_of_longest_substring @@ -6,103 +6,119 @@ from solutions.sem01.lesson06.task4 import count_unique_words -@pytest.mark.parametrize("num, expected", [ - pytest.param(1, "I", id="one"), - pytest.param(2, "II", id="two"), - pytest.param(3, "III", id="three"), - pytest.param(4, "IV", id="four"), - pytest.param(5, "V", id="five"), - pytest.param(6, "VI", id="six"), - pytest.param(9, "IX", id="nine"), - pytest.param(10, "X", id="ten"), - pytest.param(11, "XI", id="eleven"), - pytest.param(14, "XIV", id="fourteen"), - pytest.param(19, "XIX", id="nineteen"), - pytest.param(27, "XXVII", id="twenty_seven"), - pytest.param(40, "XL", id="forty"), - pytest.param(44, "XLIV", id="forty_four"), - pytest.param(50, "L", id="fifty"), - pytest.param(58, "LVIII", id="fifty_eight"), - pytest.param(90, "XC", id="ninety"), - pytest.param(99, "XCIX", id="ninety_nine"), - pytest.param(100, "C", id="hundred"), - pytest.param(400, "CD", id="four_hundred"), - pytest.param(500, "D", id="five_hundred"), - pytest.param(900, "CM", id="nine_hundred"), - pytest.param(1000, "M", id="thousand"), - pytest.param(1994, "MCMXCIV", id="mcmxciv"), - pytest.param(3999, "MMMCMXCIX", id="max_value"), - pytest.param(2023, "MMXXIII", id="current_year"), - pytest.param(1984, "MCMLXXXIV", id="classic"), -]) +@pytest.mark.parametrize( + "num, expected", + [ + pytest.param(1, "I", id="one"), + pytest.param(2, "II", id="two"), + pytest.param(3, "III", id="three"), + pytest.param(4, "IV", id="four"), + pytest.param(5, "V", id="five"), + pytest.param(6, "VI", id="six"), + pytest.param(9, "IX", id="nine"), + pytest.param(10, "X", id="ten"), + pytest.param(11, "XI", id="eleven"), + pytest.param(14, "XIV", id="fourteen"), + pytest.param(19, "XIX", id="nineteen"), + pytest.param(27, "XXVII", id="twenty_seven"), + pytest.param(40, "XL", id="forty"), + pytest.param(44, "XLIV", id="forty_four"), + pytest.param(50, "L", id="fifty"), + pytest.param(58, "LVIII", id="fifty_eight"), + pytest.param(90, "XC", id="ninety"), + pytest.param(99, "XCIX", id="ninety_nine"), + pytest.param(100, "C", id="hundred"), + pytest.param(400, "CD", id="four_hundred"), + pytest.param(500, "D", id="five_hundred"), + pytest.param(900, "CM", id="nine_hundred"), + pytest.param(1000, "M", id="thousand"), + pytest.param(1994, "MCMXCIV", id="mcmxciv"), + pytest.param(3999, "MMMCMXCIX", id="max_value"), + pytest.param(2023, "MMXXIII", id="current_year"), + pytest.param(1984, "MCMLXXXIV", id="classic"), + ], +) def test_int_to_roman(num, expected): assert int_to_roman(num) == expected -@pytest.mark.parametrize("s, expected", [ - pytest.param("", 0, id="empty_string"), - pytest.param("a", 1, id="single_char"), - pytest.param("aa", 1, id="two_same_chars"), - pytest.param("ab", 2, id="two_different_chars"), - pytest.param("abcabcbb", 3, id="classic_example_abc"), - pytest.param("bbbbb", 1, id="all_same"), - pytest.param("pwwkew", 3, id="pwwkew_example"), - pytest.param("abcdef", 6, id="all_unique"), - pytest.param("abcabcbbxyz", 4, id="long_tail_unique"), - pytest.param("aab", 2, id="aab"), - pytest.param("dvdf", 3, id="dvdf"), - pytest.param(" ", 1, id="single_space"), - pytest.param("a b c", 3, id="letters_and_spaces_unique"), - pytest.param("a b a", 3, id="space_in_middle_with_repeat"), - pytest.param("1234567890", 10, id="digits_all_unique"), - pytest.param("112233", 2, id="repeating_digits"), - pytest.param("abcdefghijklmnopqrstuvwxyz", 26, id="all_lowercase_letters"), - pytest.param("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ", 63, id="max_unique_set"), - pytest.param("a" * 10000, 1, id="ten_thousand_same"), - pytest.param("abc" * 3333 + "d", 4, id="long_repeating_with_new_char"), -]) + +@pytest.mark.parametrize( + "s, expected", + [ + pytest.param("", 0, id="empty_string"), + pytest.param("a", 1, id="single_char"), + pytest.param("aa", 1, id="two_same_chars"), + pytest.param("ab", 2, id="two_different_chars"), + pytest.param("abcabcbb", 3, id="classic_example_abc"), + pytest.param("bbbbb", 1, id="all_same"), + pytest.param("pwwkew", 3, id="pwwkew_example"), + pytest.param("abcdef", 6, id="all_unique"), + pytest.param("abcabcbbxyz", 4, id="long_tail_unique"), + pytest.param("aab", 2, id="aab"), + pytest.param("dvdf", 3, id="dvdf"), + pytest.param(" ", 1, id="single_space"), + pytest.param("a b c", 3, id="letters_and_spaces_unique"), + pytest.param("a b a", 3, id="space_in_middle_with_repeat"), + pytest.param("1234567890", 10, id="digits_all_unique"), + pytest.param("112233", 2, id="repeating_digits"), + pytest.param("abcdefghijklmnopqrstuvwxyz", 26, id="all_lowercase_letters"), + pytest.param( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ", + 63, + id="max_unique_set", + ), + pytest.param("a" * 10000, 1, id="ten_thousand_same"), + pytest.param("abc" * 3333 + "d", 4, id="long_repeating_with_new_char"), + ], +) def test_get_len_of_longest_substring(s, expected): assert get_len_of_longest_substring(s) == expected -@pytest.mark.parametrize("nums, k, expected", [ - pytest.param([23, 2, 4, 6, 7], 6, True, id="subarray_2_4_sum_6"), - pytest.param([23, 2, 6, 4, 7], 6, True, id="total_sum_42_div_by_6"), - pytest.param([23, 2, 6, 4, 7], 13, False, id="no_valid_subarray"), - pytest.param([0, 0], 1, True, id="two_zeros_any_k"), - pytest.param([1, 0], 2, False, id="length_2_sum_1_not_div_by_2"), - pytest.param([1, 2, 3], 5, True, id="subarray_2_3_sum_5"), - pytest.param([1], 1, False, id="single_element_too_short"), - pytest.param([5, 0, 0], 3, True, id="zeros_after_nonzero"), - pytest.param([1, 2], 3, True, id="exact_sum_equals_k"), - pytest.param([1, 2], 4, False, id="sum_not_divisible_by_k"), - pytest.param([0], 1, False, id="single_zero_too_short"), - pytest.param([1, 0, 2], 2, True, id="subarray_0_2_sum_2"), - pytest.param([4, 2, 4], 6, True, id="first_two_sum_6"), - pytest.param([1, 1, 1], 2, True, id="first_two_ones_sum_2"), - pytest.param([1, 2, 4, 8], 8, False, id="no_subarray_divisible_by_8"), - pytest.param([0, 1, 0], 2, False, id="zeros_with_one_sum_1"), - pytest.param([0, 1, 0, 0], 2, True, id="last_two_zeros_sum_0_div_by_any"), -]) + +@pytest.mark.parametrize( + "nums, k, expected", + [ + pytest.param([23, 2, 4, 6, 7], 6, True, id="subarray_2_4_sum_6"), + pytest.param([23, 2, 6, 4, 7], 6, True, id="total_sum_42_div_by_6"), + pytest.param([23, 2, 6, 4, 7], 13, False, id="no_valid_subarray"), + pytest.param([0, 0], 1, True, id="two_zeros_any_k"), + pytest.param([1, 0], 2, False, id="length_2_sum_1_not_div_by_2"), + pytest.param([1, 2, 3], 5, True, id="subarray_2_3_sum_5"), + pytest.param([1], 1, False, id="single_element_too_short"), + pytest.param([5, 0, 0], 3, True, id="zeros_after_nonzero"), + pytest.param([1, 2], 3, True, id="exact_sum_equals_k"), + pytest.param([1, 2], 4, False, id="sum_not_divisible_by_k"), + pytest.param([0], 1, False, id="single_zero_too_short"), + pytest.param([1, 0, 2], 2, True, id="subarray_0_2_sum_2"), + pytest.param([4, 2, 4], 6, True, id="first_two_sum_6"), + pytest.param([1, 1, 1], 2, True, id="first_two_ones_sum_2"), + pytest.param([1, 2, 4, 8], 8, False, id="no_subarray_divisible_by_8"), + pytest.param([0, 1, 0], 2, False, id="zeros_with_one_sum_1"), + pytest.param([0, 1, 0, 0], 2, True, id="last_two_zeros_sum_0_div_by_any"), + ], +) def test_is_there_any_good_subarray(nums, k, expected): assert is_there_any_good_subarray(nums, k) == expected -import pytest - -@pytest.mark.parametrize("text, expected", [ - pytest.param("", 0, id="empty_string"), - pytest.param(" ", 0, id="only_spaces"), - pytest.param("hello", 1, id="single_word"), - pytest.param("Hello hello", 1, id="case_insensitive"), - pytest.param("Hello, world!", 2, id="punctuation_around"), - pytest.param("Hello, hello, world!", 2, id="duplicates_with_punct"), - pytest.param("The quick brown fox jumps over the lazy dog.", 8, id="classic_pangram"), - pytest.param("!!! ???", 0, id="only_punctuation"), - pytest.param("word1 word2 word1", 2, id="digits_in_words"), - pytest.param("Don't stop believing!", 3, id="apostrophe_inside"), - pytest.param(" Hello , World ! ", 2, id="extra_whitespace"), - pytest.param("A a A a", 1, id="repeated_case_variants"), - pytest.param("word... word!!!", 1, id="multiple_punct_at_end"), - pytest.param("123 456 123", 2, id="numbers_as_words"), -]) +@pytest.mark.parametrize( + "text, expected", + [ + pytest.param("", 0, id="empty_string"), + pytest.param(" ", 0, id="only_spaces"), + pytest.param("hello", 1, id="single_word"), + pytest.param("Hello hello", 1, id="case_insensitive"), + pytest.param("Hello, world!", 2, id="punctuation_around"), + pytest.param("Hello, hello, world!", 2, id="duplicates_with_punct"), + pytest.param("The quick brown fox jumps over the lazy dog.", 8, id="classic_pangram"), + pytest.param("!!! ???", 0, id="only_punctuation"), + pytest.param("word1 word2 word1", 2, id="digits_in_words"), + pytest.param("Don't stop believing!", 3, id="apostrophe_inside"), + pytest.param(" Hello , World ! ", 2, id="extra_whitespace"), + pytest.param("A a A a", 1, id="repeated_case_variants"), + pytest.param("word... word!!!", 1, id="multiple_punct_at_end"), + pytest.param("123 456 123", 2, id="numbers_as_words"), + ], +) def test_count_unique_words(text, expected): - assert count_unique_words(text) == expected \ No newline at end of file + assert count_unique_words(text) == expected diff --git a/deprecated_tests/sem01/tests/test_lesson08_tasks.py b/deprecated_tests/sem01/tests/test_lesson08_tasks.py index 962ba4bd2..06f04319d 100644 --- a/deprecated_tests/sem01/tests/test_lesson08_tasks.py +++ b/deprecated_tests/sem01/tests/test_lesson08_tasks.py @@ -1,10 +1,10 @@ -import pytest import math import time from solutions.sem01.lesson08.task1 import make_averager from solutions.sem01.lesson08.task2 import collect_statistic + def test_make_averager(): get_avg = make_averager(2) @@ -15,6 +15,7 @@ def test_make_averager(): assert math.isclose(get_avg(5), 1) assert math.isclose(get_avg(5), 5) + def test_make_averager2(): get_avg = make_averager(5) @@ -27,6 +28,7 @@ def test_make_averager2(): assert math.isclose(get_avg(-7), 0) assert math.isclose(get_avg(-2), -1) + def test_collect_statistic(): statistics: list[str, list[float, int]] = {} @@ -37,7 +39,7 @@ def func1() -> None: @collect_statistic(statistics) def func2() -> None: time.sleep(0.1) - + for _ in range(3): func1() @@ -58,10 +60,11 @@ def test_collect_statistic_inout(): @collect_statistic(statistics) def func(a, b, *, c, d): return a + b + c + d - + assert func(1, 2, c=3, d=4) == 10 assert statistics[func.__name__][1] == 1 + def test_collect_statistic_count_call(): statistics: list[str, list[float, int]] = {} @@ -76,7 +79,7 @@ def func(): count_call += 1 return func - + func = func_fab() func() - assert statistics[func.__name__][1] == 1 \ No newline at end of file + assert statistics[func.__name__][1] == 1 diff --git a/deprecated_tests/sem01/tests/test_lesson11_tasks.py b/deprecated_tests/sem01/tests/test_lesson11_tasks.py index d0cd02efc..941eed777 100644 --- a/deprecated_tests/sem01/tests/test_lesson11_tasks.py +++ b/deprecated_tests/sem01/tests/test_lesson11_tasks.py @@ -1,9 +1,8 @@ -import pytest - import math import sys from io import StringIO +import pytest from solutions.sem01.lesson11.task1 import Vector2D @@ -84,13 +83,12 @@ def test_print(): sys.stdout = old_stdout output = captured_output.getvalue() assert ( - output == "Vector2D(abscissa=1, ordinate=-2)" + output == "Vector2D(abscissa=1, ordinate=-2)" or output == "Vector2D(abscissa=1., ordinate=-2.)" - or output == "Vector2D(abscissa=1.0, ordinate=-2.0)" + or output == "Vector2D(abscissa=1.0, ordinate=-2.0)" ) - @pytest.mark.parametrize( "abscissa1, ordinate1, abscissa2, ordinate2, expected", [ diff --git a/deprecated_tests/sem01/tests_hw/test_hw1_tasks.py b/deprecated_tests/sem01/tests_hw/test_hw1_tasks.py index 0ecf8a108..c600f76d5 100644 --- a/deprecated_tests/sem01/tests_hw/test_hw1_tasks.py +++ b/deprecated_tests/sem01/tests_hw/test_hw1_tasks.py @@ -1,17 +1,15 @@ -import pytest import uuid -from unittest.mock import MagicMock, patch, Mock +from unittest.mock import MagicMock, Mock, patch + +import pytest -from homeworks.sem01.hw1.aggregate_segmentation import aggregate_segmentation, ALLOWED_TYPES +from homeworks.sem01.hw1.aggregate_segmentation import ALLOWED_TYPES, aggregate_segmentation from homeworks.sem01.hw1.backoff import backoff from homeworks.sem01.hw1.cache import lru_cache from homeworks.sem01.hw1.convert_exception import convert_exceptions_to_api_compitable_ones -from .hw1_test_data.cache_test_data import ( - TESTCASE_DATA, - TESTCASE_IDS, -) -NAME_BACKOFF_MODULE = "homeworks.hw1.backoff" # название модуля с backoff +NAME_BACKOFF_MODULE = "homeworks.hw1.backoff" # название модуля с backoff + def test_valid_segments() -> None: """Тест: валидные сегменты.""" @@ -32,53 +30,53 @@ def test_valid_segments() -> None: "segment_id": segment_id_1, "segment_start": 0.0, "segment_end": 1.0, - "type": list_allow_types[0] + "type": list_allow_types[0], }, { "audio_id": audio_id_1, "segment_id": segment_id_2, "segment_start": 2.5, "segment_end": 3.5, - "type": list_allow_types[1] + "type": list_allow_types[1], }, { "audio_id": audio_id_2, "segment_id": segment_id_3, "segment_start": 4.5, "segment_end": 4.6, - "type": list_allow_types[0] + "type": list_allow_types[0], }, { "audio_id": audio_id_2, "segment_id": segment_id_4, "segment_start": 5.5, "segment_end": 6.5, - "type": list_allow_types[1] + "type": list_allow_types[1], }, { "audio_id": audio_id_3, "segment_id": segment_id_5, "segment_start": None, "segment_end": None, - "type": None + "type": None, }, { "audio_id": "audio3", "segment_id": "seg5", "segment_start": 0.0, "segment_end": 1.0, - "type": "invalid_type" + "type": "invalid_type", }, ] expected_valid = { audio_id_1: { segment_id_1: {"start": 0.0, "end": 1.0, "type": list_allow_types[0]}, - segment_id_2: {"start": 2.5, "end": 3.5, "type": list_allow_types[1]} + segment_id_2: {"start": 2.5, "end": 3.5, "type": list_allow_types[1]}, }, audio_id_2: { segment_id_3: {"start": 4.5, "end": 4.6, "type": list_allow_types[0]}, - segment_id_4: {"start": 5.5, "end": 6.5, "type": list_allow_types[1]} + segment_id_4: {"start": 5.5, "end": 6.5, "type": list_allow_types[1]}, }, audio_id_3: {}, } @@ -88,6 +86,7 @@ def test_valid_segments() -> None: assert result_valid == expected_valid assert result_forbidden == expected_forbidden + def test_convert_matching_exception() -> None: """Тест: исключение заменяется на API-совместимое.""" @@ -97,7 +96,7 @@ class ApiValueError(Exception): @convert_exceptions_to_api_compitable_ones({ValueError: ApiValueError}) def func(): raise ValueError("Внутренняя ошибка") - + @convert_exceptions_to_api_compitable_ones({ValueError: ApiValueError}) def func2(): raise KeyError("Внутренняя ошибка") @@ -108,7 +107,8 @@ def func2(): with pytest.raises(KeyError): func2() -@patch(NAME_BACKOFF_MODULE + '.sleep') + +@patch(NAME_BACKOFF_MODULE + ".sleep") def test_exponential_backoff_and_jitter(mock_sleep: MagicMock) -> None: """Тест: задержки увеличиваются, но не выше timeout_max и к ним добавляется дрожь.""" attempts = 0 @@ -116,12 +116,7 @@ def test_exponential_backoff_and_jitter(mock_sleep: MagicMock) -> None: retry_amount = 4 timeouts = [1, 2, 4, 4] - @backoff( - retry_amount=retry_amount, - timeout_start=1, - timeout_max=timeout_max, - backoff_scale=2.0 - ) + @backoff(retry_amount=retry_amount, timeout_start=1, timeout_max=timeout_max, backoff_scale=2.0) def func(): nonlocal attempts attempts += 1 @@ -138,22 +133,23 @@ def func(): for av_time, args in zip(timeouts, args_list): count_more_av_time += args > av_time assert av_time <= args <= av_time + 0.5 - - assert count_more_av_time # есть добавление "дрожи" + + assert count_more_av_time # есть добавление "дрожи" + def test_success() -> None: capacity = 2 - call_args = [ + call_args = [ (1, 2), (1, 2), (2, 2), ] call_count_expected = 2 - + mock_func = Mock() func_cached = lru_cache(capacity=capacity)(mock_func) for args in call_args: func_cached(args) - assert mock_func.call_count == call_count_expected \ No newline at end of file + assert mock_func.call_count == call_count_expected diff --git a/deprecated_tests/sem02/tests/task4/test_lesson04_tasks.py b/deprecated_tests/sem02/tests/task4/test_lesson04_tasks.py index 37d249b77..81312dea0 100644 --- a/deprecated_tests/sem02/tests/task4/test_lesson04_tasks.py +++ b/deprecated_tests/sem02/tests/task4/test_lesson04_tasks.py @@ -333,12 +333,8 @@ class TestTask2: ), ], ) - def test_get_dominant_color_info( - self, image, threshold, expected_color, expected_ratio - ): - color, ratio_percent = get_dominant_color_info( - image.astype(np.uint8), threshold - ) + def test_get_dominant_color_info(self, image, threshold, expected_color, expected_ratio): + color, ratio_percent = get_dominant_color_info(image.astype(np.uint8), threshold) assert color in expected_color assert (abs(ratio_percent - expected_ratio * 100) < 1e-6) or ( diff --git a/deprecated_tests/sem02/tests/task5/test_lesson05_tasks.py b/deprecated_tests/sem02/tests/task5/test_lesson05_tasks.py index aeb37ebcc..4cc9188d4 100644 --- a/deprecated_tests/sem02/tests/task5/test_lesson05_tasks.py +++ b/deprecated_tests/sem02/tests/task5/test_lesson05_tasks.py @@ -89,9 +89,7 @@ class TestTask1: ), ], ) - def test_can_satisfy_demand( - self, costs, resource_amounts, demand_expected, expected - ): + def test_can_satisfy_demand(self, costs, resource_amounts, demand_expected, expected): assert can_satisfy_demand(costs, resource_amounts, demand_expected) == expected def test_can_satisfy_demand_validate(self): @@ -172,9 +170,7 @@ class TestTask2: ), ], ) - def test_get_projections_components( - self, matrix, vector, proj_expected, orth_expected - ): + def test_get_projections_components(self, matrix, vector, proj_expected, orth_expected): projections, orthogonals = get_projections_components(matrix, vector) if proj_expected is None: diff --git a/homeworks/sem01/hw1/backoff.py b/homeworks/sem01/hw1/backoff.py index 696ffa73a..a3373a982 100644 --- a/homeworks/sem01/hw1/backoff.py +++ b/homeworks/sem01/hw1/backoff.py @@ -1,5 +1,3 @@ -from random import uniform -from time import sleep from typing import ( Callable, ParamSpec, diff --git a/solutions/sem01/lesson02/task3.py b/solutions/sem01/lesson02/task3.py index ee2a84ecf..70b17f86e 100644 --- a/solutions/sem01/lesson02/task3.py +++ b/solutions/sem01/lesson02/task3.py @@ -1,4 +1,4 @@ def get_amount_of_ways_to_climb(stair_amount: int) -> int: step_prev, step_curr = 1, 1 # ваш код - return step_curr + return step_curr, step_prev diff --git a/solutions/sem01/lesson03/task1.py b/solutions/sem01/lesson03/task1.py index f1d8fe26b..7b048e654 100644 --- a/solutions/sem01/lesson03/task1.py +++ b/solutions/sem01/lesson03/task1.py @@ -1,3 +1,3 @@ def flip_bits_in_range(num: int, left_bit: int, right_bit: int) -> int: # ваш код - return num \ No newline at end of file + return num diff --git a/solutions/sem01/lesson03/task2.py b/solutions/sem01/lesson03/task2.py index a3a738c2a..5cf2b6316 100644 --- a/solutions/sem01/lesson03/task2.py +++ b/solutions/sem01/lesson03/task2.py @@ -1,3 +1,3 @@ def get_cube_root(n: float, eps: float) -> float: # ваш код - return n \ No newline at end of file + return n diff --git a/solutions/sem01/lesson04/task1.py b/solutions/sem01/lesson04/task1.py index 47384423a..0135e399b 100644 --- a/solutions/sem01/lesson04/task1.py +++ b/solutions/sem01/lesson04/task1.py @@ -1,3 +1,3 @@ def is_arithmetic_progression(lst: list[list[int]]) -> bool: # ваш код - return False \ No newline at end of file + return False diff --git a/solutions/sem01/lesson04/task2.py b/solutions/sem01/lesson04/task2.py index 4591d0a3e..5d6f8ee8a 100644 --- a/solutions/sem01/lesson04/task2.py +++ b/solutions/sem01/lesson04/task2.py @@ -1,3 +1,3 @@ def merge_intervals(intervals: list[list[int, int]]) -> list[list[int, int]]: # ваш код - return [[0,0]] \ No newline at end of file + return [[0, 0]] diff --git a/solutions/sem01/lesson04/task4.py b/solutions/sem01/lesson04/task4.py index b21bc5a39..2664384d8 100644 --- a/solutions/sem01/lesson04/task4.py +++ b/solutions/sem01/lesson04/task4.py @@ -1,3 +1,3 @@ def move_zeros_to_end(nums: list[int]) -> list[int]: # ваш код - return 0 \ No newline at end of file + return 0 diff --git a/solutions/sem01/lesson04/task5.py b/solutions/sem01/lesson04/task5.py index 02d7742bb..ec6932ee4 100644 --- a/solutions/sem01/lesson04/task5.py +++ b/solutions/sem01/lesson04/task5.py @@ -1,3 +1,3 @@ def find_row_with_most_ones(matrix: list[list[int]]) -> int: # ваш код - return 0 \ No newline at end of file + return 0 diff --git a/solutions/sem01/lesson04/task6.py b/solutions/sem01/lesson04/task6.py index 16df27ca6..d16e77dda 100644 --- a/solutions/sem01/lesson04/task6.py +++ b/solutions/sem01/lesson04/task6.py @@ -1,3 +1,3 @@ -def count_cycles(arr: list[int]) -> int: +def count_cycles(arr: list[int]) -> int: # ваш код - return 0 \ No newline at end of file + return 0 diff --git a/solutions/sem01/lesson05/task1.py b/solutions/sem01/lesson05/task1.py index 9a17211e5..fdf3b5488 100644 --- a/solutions/sem01/lesson05/task1.py +++ b/solutions/sem01/lesson05/task1.py @@ -1,3 +1,3 @@ def is_palindrome(text: str) -> bool: # ваш код - return False \ No newline at end of file + return False diff --git a/solutions/sem01/lesson05/task2.py b/solutions/sem01/lesson05/task2.py index 367503802..c70b40298 100644 --- a/solutions/sem01/lesson05/task2.py +++ b/solutions/sem01/lesson05/task2.py @@ -1,3 +1,3 @@ def are_anagrams(word1: str, word2: str) -> bool: # ваш код - return False \ No newline at end of file + return False diff --git a/solutions/sem01/lesson05/task4.py b/solutions/sem01/lesson05/task4.py index 4c4e9086e..7c2c26f17 100644 --- a/solutions/sem01/lesson05/task4.py +++ b/solutions/sem01/lesson05/task4.py @@ -1,3 +1,3 @@ def unzip(compress_text: str) -> str: # ваш код - return compress_text \ No newline at end of file + return compress_text diff --git a/solutions/sem01/lesson05/task5.py b/solutions/sem01/lesson05/task5.py index 076c5bb6c..da9e6d08c 100644 --- a/solutions/sem01/lesson05/task5.py +++ b/solutions/sem01/lesson05/task5.py @@ -1,3 +1,3 @@ -def reg_validator(reg_expr: str, text: str) -> bool: +def reg_validator(reg_expr: str, text: str) -> bool: # ваш код - return False \ No newline at end of file + return False diff --git a/solutions/sem01/lesson05/task6.py b/solutions/sem01/lesson05/task6.py index 1b914ada7..63207797d 100644 --- a/solutions/sem01/lesson05/task6.py +++ b/solutions/sem01/lesson05/task6.py @@ -1,3 +1,3 @@ def simplify_path(path: str) -> str: # ваш код - return path \ No newline at end of file + return path diff --git a/solutions/sem01/lesson06/task1.py b/solutions/sem01/lesson06/task1.py index 2d1e30e96..353cb3616 100644 --- a/solutions/sem01/lesson06/task1.py +++ b/solutions/sem01/lesson06/task1.py @@ -1,3 +1,3 @@ def int_to_roman(num: int) -> str: # ваш код - return "" \ No newline at end of file + return "" diff --git a/solutions/sem01/lesson06/task2.py b/solutions/sem01/lesson06/task2.py index f535b5a0c..f1034e24e 100644 --- a/solutions/sem01/lesson06/task2.py +++ b/solutions/sem01/lesson06/task2.py @@ -1,3 +1,3 @@ def get_len_of_longest_substring(text: str) -> int: # ваш код - return 0 \ No newline at end of file + return 0 diff --git a/solutions/sem01/lesson06/task3.py b/solutions/sem01/lesson06/task3.py index 7449a1e72..b160d615a 100644 --- a/solutions/sem01/lesson06/task3.py +++ b/solutions/sem01/lesson06/task3.py @@ -2,6 +2,5 @@ def is_there_any_good_subarray( nums: list[int], k: int, ) -> bool: - # ваш код return False diff --git a/solutions/sem01/lesson06/task4.py b/solutions/sem01/lesson06/task4.py index 5b75a110c..95a7098e4 100644 --- a/solutions/sem01/lesson06/task4.py +++ b/solutions/sem01/lesson06/task4.py @@ -1,3 +1,3 @@ def count_unique_words(text: str) -> int: # ваш код - return 0 \ No newline at end of file + return 0 diff --git a/solutions/sem01/lesson08/task1.py b/solutions/sem01/lesson08/task1.py index 4390f6c84..7fa724ef8 100644 --- a/solutions/sem01/lesson08/task1.py +++ b/solutions/sem01/lesson08/task1.py @@ -1,5 +1,6 @@ from typing import Callable + def make_averager(accumulation_period: int) -> Callable[[float], float]: # ваш код - pass \ No newline at end of file + pass diff --git a/solutions/sem01/lesson08/task2.py b/solutions/sem01/lesson08/task2.py index 6e4af8707..cc2ae4303 100644 --- a/solutions/sem01/lesson08/task2.py +++ b/solutions/sem01/lesson08/task2.py @@ -2,9 +2,7 @@ T = TypeVar("T") -def collect_statistic( - statistics: dict[str, list[float, int]] -) -> Callable[[T], T]: - + +def collect_statistic(statistics: dict[str, list[float, int]]) -> Callable[[T], T]: # ваш код - pass \ No newline at end of file + pass diff --git a/solutions/sem01/lesson12/task3.py b/solutions/sem01/lesson12/task3.py index 64c112ccc..58b0986e1 100644 --- a/solutions/sem01/lesson12/task3.py +++ b/solutions/sem01/lesson12/task3.py @@ -1,6 +1,3 @@ -import sys - - class FileOut: def __init__( self, diff --git a/solutions/sem02/lesson05/task1.py b/solutions/sem02/lesson05/task1.py index e9c7c3c56..364968405 100644 --- a/solutions/sem02/lesson05/task1.py +++ b/solutions/sem02/lesson05/task1.py @@ -9,4 +9,14 @@ def can_satisfy_demand( costs: np.ndarray, resource_amounts: np.ndarray, demand_expected: np.ndarray, -) -> bool: ... +) -> bool: + if costs.shape[0] != resource_amounts.shape[0] or demand_expected.shape[0] != costs.shape[1]: + raise ShapeMismatchError() + + requiered_resources = demand_expected @ costs.T + mask = requiered_resources <= resource_amounts + res = resource_amounts[mask] + + if res.shape[0] == resource_amounts.shape[0]: + return True + return False diff --git a/solutions/sem02/lesson05/task2.py b/solutions/sem02/lesson05/task2.py index be1fb9d2b..e26680abb 100644 --- a/solutions/sem02/lesson05/task2.py +++ b/solutions/sem02/lesson05/task2.py @@ -8,4 +8,18 @@ class ShapeMismatchError(Exception): def get_projections_components( matrix: np.ndarray, vector: np.ndarray, -) -> tuple[np.ndarray | None, np.ndarray | None]: ... +) -> tuple[np.ndarray | None, np.ndarray | None]: + if matrix.shape[1] != vector.shape[0]: + raise ShapeMismatchError() + if matrix.shape[0] != matrix.shape[1]: + raise ShapeMismatchError() + + if np.linalg.slogdet(matrix)[0] == 0: + return (None, None) + + coef1 = matrix @ vector + coef2 = np.sum(matrix**2, axis=1) + coef = coef1 / coef2 + proekcii = matrix * coef[..., np.newaxis] + ort_sost = vector - proekcii + return (proekcii, ort_sost) diff --git a/solutions/sem02/lesson05/task3.py b/solutions/sem02/lesson05/task3.py index 0c66906cb..21ba82dc8 100644 --- a/solutions/sem02/lesson05/task3.py +++ b/solutions/sem02/lesson05/task3.py @@ -9,4 +9,16 @@ def adaptive_filter( Vs: np.ndarray, Vj: np.ndarray, diag_A: np.ndarray, -) -> np.ndarray: ... +) -> np.ndarray: + if Vs.shape[0] != Vj.shape[0]: + raise ShapeMismatchError() + if diag_A.shape[0] != Vj.shape[1]: + raise ShapeMismatchError() + + M, K = Vj.shape + A = np.diag(diag_A) + Vjh = Vj.conj().T + + skobka = np.eye(K) + Vjh @ Vj @ A + y = Vs - Vj @ np.linalg.solve(skobka, Vjh @ Vs) + return y From 00322d215edfed3577d3721f387a7abfb941583e Mon Sep 17 00:00:00 2001 From: kiritonator Date: Fri, 10 Apr 2026 23:46:33 +0300 Subject: [PATCH 2/4] done task lesson07 --- .idea/.name | 1 + .../lesson07/mitral_disease_analysis.png | Bin 0 -> 73198 bytes solutions/sem02/lesson07/task1.py | 97 +++++++++++++++++- solutions/sem02/lesson07/task2.py | 55 +++++++++- 4 files changed, 149 insertions(+), 4 deletions(-) create mode 100644 .idea/.name create mode 100644 solutions/sem02/lesson07/mitral_disease_analysis.png diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 000000000..f31b987f1 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +task3.py \ No newline at end of file diff --git a/solutions/sem02/lesson07/mitral_disease_analysis.png b/solutions/sem02/lesson07/mitral_disease_analysis.png new file mode 100644 index 0000000000000000000000000000000000000000..f51bada388470a45e08b5609489d025197b9176a GIT binary patch literal 73198 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 F|118_QIP-u literal 0 HcmV?d00001 diff --git a/solutions/sem02/lesson07/task1.py b/solutions/sem02/lesson07/task1.py index 3a505d89b..62c99f479 100644 --- a/solutions/sem02/lesson07/task1.py +++ b/solutions/sem02/lesson07/task1.py @@ -13,8 +13,99 @@ def visualize_diagrams( ordinates: np.ndarray, diagram_type: Any, ) -> None: - # ваш код - pass + if abscissa.shape[0] != ordinates.shape[0]: + raise ShapeMismatchError() + if diagram_type not in ["hist", "violin", "box"]: + raise ValueError() + + figure = plt.figure(figsize=(8, 8)) + grid = plt.GridSpec(4, 4, wspace=space, hspace=space) + + axis_scatter = figure.add_subplot(grid[:-1, 1:]) + + axis_vert = figure.add_subplot( + grid[:-1, 0], + sharey=axis_scatter, + ) + axis_hor = figure.add_subplot( + grid[-1, 1:], + sharex=axis_scatter, + ) + + axis_scatter.scatter(abscissa, ordinates, color="aquamarine", alpha=0.5) + + if diagram_type == "hist": + axis_hor.hist( + abscissa, + bins=50, + color="aquamarine", + density=True, + alpha=0.5, + ) + axis_vert.hist( + ordinates, + bins=50, + color="aquamarine", + orientation="horizontal", + density=True, + alpha=0.5, + ) + axis_hor.invert_yaxis() + axis_vert.invert_xaxis() + + if diagram_type == "violin": + violin_parts_hor = axis_hor.violinplot( + abscissa, + vert=False, + showmedians=True, + ) + + for body in violin_parts_hor["bodies"]: + body.set_facecolor("aquamarine") + body.set_edgecolor("green") + + for part in violin_parts_hor: + if part == "bodies": + continue + + violin_parts_hor[part].set_edgecolor("aquamarine") + + violin_parts_vert = axis_vert.violinplot( + ordinates, + vert=True, + showmedians=True, + ) + + for body in violin_parts_vert["bodies"]: + body.set_facecolor("aquamarine") + body.set_edgecolor("green") + + for part in violin_parts_vert: + if part == "bodies": + continue + + violin_parts_vert[part].set_edgecolor("aquamarine") + + axis_vert.set_yticks([]) + axis_hor.set_yticks([]) + + if diagram_type == "box": + axis_hor.boxplot( + abscissa, + vert=False, + patch_artist=True, + boxprops=dict(facecolor="mediumseagreen"), + medianprops=dict(color="k"), + ) + axis_vert.boxplot( + ordinates, + vert=True, + patch_artist=True, + boxprops=dict(facecolor="mediumseagreen"), + medianprops=dict(color="k"), + ) + axis_vert.set_yticks([]) + axis_hor.set_yticks([]) if __name__ == "__main__": @@ -24,5 +115,5 @@ def visualize_diagrams( abscissa, ordinates = np.random.multivariate_normal(mean, cov, size=1000).T - visualize_diagrams(abscissa, ordinates, "hist") + visualize_diagrams(abscissa, ordinates, "box") plt.show() diff --git a/solutions/sem02/lesson07/task2.py b/solutions/sem02/lesson07/task2.py index decd607ef..265ac566d 100644 --- a/solutions/sem02/lesson07/task2.py +++ b/solutions/sem02/lesson07/task2.py @@ -1 +1,54 @@ -# ваш код (используйте функции или классы для решения данной задачи) +import json + +import matplotlib.pyplot as plt +import numpy as np + + +def mitrial_disiase_analyse(path_to_json): + with open(path_to_json, "r", encoding="utf-8") as f: + data_dict = json.load(f) + + before = np.array(data_dict["before"]) + after = np.array(data_dict["after"]) + + _, before_counts = np.unique(before, return_counts=True) + _, after_counts = np.unique(after, return_counts=True) + + figure, axis = plt.subplots(figsize=(9, 9)) + axis: plt.Axes + + labels = np.array(["I", "II", "III", "IV"]) + + x = np.arange(labels.size) + width = 0.35 + + axis.set_title("mitrial disiase analyse", fontsize=17, fontweight="bold", c="dimgray") + axis.set_ylabel("amount of people", fontsize=14, fontweight="bold", c="dimgray") + + axis.bar( + x - width / 2, + before_counts, + width=width, + color="red", + edgecolor="red", + label="before", + ) + axis.bar( + x + width / 2, + after_counts, + width=width, + color="blue", + edgecolor="blue", + label="after", + ) + + axis.set_xticks(x, labels=labels, weight="bold") + axis.tick_params(axis="x", labelsize=14, labelcolor="dimgray") + axis.legend() + + plt.savefig("mitral_disease_analysis.png", dpi=300, bbox_inches="tight") + + +# количество людей с терпимыми стадиями (1-2) увеличилось, +# а количество людей с опасными стадиями (3-4) уменьшилось, +# значит все в порядке и импланты работают как надо From 10b920b59b4bea52fe0d40f11dc6159eb220ec7f Mon Sep 17 00:00:00 2001 From: kiritonator Date: Fri, 17 Apr 2026 23:25:25 +0300 Subject: [PATCH 3/4] done les8 --- solutions/sem02/lesson08/task1.py | 42 +++++++++- solutions/sem02/lesson08/task2.py | 123 +++++++++++++++++++++++++++++- 2 files changed, 161 insertions(+), 4 deletions(-) diff --git a/solutions/sem02/lesson08/task1.py b/solutions/sem02/lesson08/task1.py index 89f88572f..c6b3764ab 100644 --- a/solutions/sem02/lesson08/task1.py +++ b/solutions/sem02/lesson08/task1.py @@ -16,8 +16,46 @@ def create_modulation_animation( animation_step=0.01, save_path="" ) -> FuncAnimation: - # ваш код - return FuncAnimation() + abscissa = np.arange(0, plot_duration, time_step) + + if modulation is None: + def modulation(t): + return np.ones_like(t) + + + def update_frame( + frame_id: int, + *, + line: plt.Line2D, + ) -> tuple[plt.Line2D]: + n_abscissa = abscissa + frame_id * animation_step + ordinates = modulation(n_abscissa) * np.sin(2 * np.pi * fc * n_abscissa) + line.set_data(n_abscissa, ordinates) + axis.set_xlim(n_abscissa.min(), n_abscissa.max()) + return line, + + figure, axis = plt.subplots(figsize=(16, 9)) + axis: plt.Axes + + axis.set_xlim(abscissa.min(), abscissa.max()) + line, *_ = axis.plot( + abscissa, + modulation(abscissa) * np.sin(2* np.pi * fc *abscissa), + c="green", + ) + + animation = FuncAnimation( + figure, + partial(update_frame, line=line), + frames=num_frames, + interval=animation_step, + blit=False, + ) + if save_path != "": + animation.save(save_path, writer="pillow") + + plt.close(figure) + return animation if __name__ == "__main__": diff --git a/solutions/sem02/lesson08/task2.py b/solutions/sem02/lesson08/task2.py index b677c0702..c77d5fbd7 100644 --- a/solutions/sem02/lesson08/task2.py +++ b/solutions/sem02/lesson08/task2.py @@ -15,8 +15,127 @@ def animate_wave_algorithm( end: tuple[int, int], save_path: str = "" ) -> FuncAnimation: - # ваш код - return FuncAnimation() + rows = maze.shape[0] + cols = maze.shape[1] + sr, sc = start + er, ec = end + dist = -np.ones_like(maze, dtype=int) + dist[sr, sc] = 0 + current_front = [(sr, sc)] + + wave_frames = [dist.copy()] + + possible_move = [(-1, 0), (1, 0), (0, -1), (0, 1)] + reached = False + + while current_front: + next_front = [] + + for r, c in current_front: + if (r, c) == end: + reached = True + break + + for dr, dc in possible_move: + nr, nc = r + dr, c + dc + if 0 <= nr < rows and 0 <= nc < cols: + if maze[nr, nc] == 1 and dist[nr, nc] == -1: + dist[nr, nc] = dist[r, c] + 1 + next_front.append((nr, nc)) + wave_frames.append(dist.copy()) + + if reached: + break + current_front = next_front + + path_mask = np.zeros_like(maze, dtype=bool) + if reached: + r, c = end + path_mask[r, c] = True + d = dist[r, c] + while (r, c) != (sr, sc): + found = False + for dr, dc in possible_move: + nr, nc = r + dr, c + dc + if 0 <= nr < rows and 0 <= nc < cols: + if dist[nr, nc] == d - 1: + r, c = nr, nc + d -= 1 + path_mask[r, c] = True + found = True + break + + def make_image(frame, show_path=False): + h, w = frame.shape + img = np.zeros((h, w, 3), dtype=float) + img[:, :] = (1.0, 1.0, 1.0) + walls = (maze == 0) + img[walls] = (0.0, 0.0, 0.0) + visited = frame >= 0 + img[visited] = (0.53, 0.81, 0.98) + + if show_path: + path_and_visited = path_mask & (frame >= 0) + img[path_and_visited] = (1.0, 0.8, 0.0) + + img[sr, sc] = (0.0, 1.0, 0.0) + img[er, ec] = (1.0, 0.0, 0.0) + + return img + + fig, ax = plt.subplots(figsize=(5, 5)) + ax.set_xticks([]) + ax.set_yticks([]) + + im = ax.imshow(make_image(wave_frames[0]), interpolation="nearest") + + texts = [] + + total_wave_frames = len(wave_frames) + extra_frames = 15 + total_frames = total_wave_frames + extra_frames + + def update(frame_id): + ax.set_title("Волна") + if frame_id < total_wave_frames: + frame = wave_frames[frame_id] + show_path = False + else: + frame = wave_frames[-1] + show_path = reached + + im.set_data(make_image(frame, show_path=show_path)) + + for r in range(rows): + for c in range(cols): + if frame[r, c] >= 0: + txt = ax.text( + c, + r, + str(frame[r, c]), + ha="center", + va="center", + fontsize=8, + color="black", + ) + texts.append(txt) + + return (im, *texts) + + anim = FuncAnimation( + fig, + update, + frames=total_frames, + interval=500, + blit=False, + ) + + + if save_path: + anim.save(save_path, writer="pillow") + + plt.close(fig) + return anim if __name__ == "__main__": # Пример 1 From 233692389cb94c4d452438646e191d330bcff1b6 Mon Sep 17 00:00:00 2001 From: kiritonator Date: Fri, 17 Apr 2026 23:41:29 +0300 Subject: [PATCH 4/4] done les8 FIXED --- solutions/sem02/lesson08/task1.py | 38 +++++++++++--------------- solutions/sem02/lesson08/task2.py | 44 ++++++++++++------------------- 2 files changed, 33 insertions(+), 49 deletions(-) diff --git a/solutions/sem02/lesson08/task1.py b/solutions/sem02/lesson08/task1.py index c6b3764ab..ff28784f4 100644 --- a/solutions/sem02/lesson08/task1.py +++ b/solutions/sem02/lesson08/task1.py @@ -2,37 +2,30 @@ import matplotlib.pyplot as plt import numpy as np - from IPython.display import HTML from matplotlib.animation import FuncAnimation def create_modulation_animation( - modulation, - fc, - num_frames, - plot_duration, - time_step=0.001, - animation_step=0.01, - save_path="" + modulation, fc, num_frames, plot_duration, time_step=0.001, animation_step=0.01, save_path="" ) -> FuncAnimation: abscissa = np.arange(0, plot_duration, time_step) if modulation is None: + def modulation(t): return np.ones_like(t) - def update_frame( - frame_id: int, - *, - line: plt.Line2D, + frame_id: int, + *, + line: plt.Line2D, ) -> tuple[plt.Line2D]: n_abscissa = abscissa + frame_id * animation_step ordinates = modulation(n_abscissa) * np.sin(2 * np.pi * fc * n_abscissa) line.set_data(n_abscissa, ordinates) axis.set_xlim(n_abscissa.min(), n_abscissa.max()) - return line, + return (line,) figure, axis = plt.subplots(figsize=(16, 9)) axis: plt.Axes @@ -40,7 +33,7 @@ def update_frame( axis.set_xlim(abscissa.min(), abscissa.max()) line, *_ = axis.plot( abscissa, - modulation(abscissa) * np.sin(2* np.pi * fc *abscissa), + modulation(abscissa) * np.sin(2 * np.pi * fc * abscissa), c="green", ) @@ -59,14 +52,15 @@ def update_frame( if __name__ == "__main__": + def modulation_function(t): - return np.cos(t * 6) + return np.cos(t * 6) - num_frames = 100 - plot_duration = np.pi / 2 - time_step = 0.001 - animation_step = np.pi / 200 - fc = 50 + num_frames = 100 + plot_duration = np.pi / 2 + time_step = 0.001 + animation_step = np.pi / 200 + fc = 50 save_path_with_modulation = "modulated_signal.gif" animation = create_modulation_animation( @@ -76,6 +70,6 @@ def modulation_function(t): plot_duration=plot_duration, time_step=time_step, animation_step=animation_step, - save_path=save_path_with_modulation + save_path=save_path_with_modulation, ) - HTML(animation.to_jshtml()) \ No newline at end of file + HTML(animation.to_jshtml()) diff --git a/solutions/sem02/lesson08/task2.py b/solutions/sem02/lesson08/task2.py index c77d5fbd7..a0200be57 100644 --- a/solutions/sem02/lesson08/task2.py +++ b/solutions/sem02/lesson08/task2.py @@ -1,20 +1,12 @@ -from functools import partial - import matplotlib.pyplot as plt import numpy as np - from IPython.display import HTML from matplotlib.animation import FuncAnimation - - def animate_wave_algorithm( - maze: np.ndarray, - start: tuple[int, int], - end: tuple[int, int], - save_path: str = "" -) -> FuncAnimation: + maze: np.ndarray, start: tuple[int, int], end: tuple[int, int], save_path: str = "" +) -> FuncAnimation: rows = maze.shape[0] cols = maze.shape[1] sr, sc = start @@ -54,7 +46,6 @@ def animate_wave_algorithm( path_mask[r, c] = True d = dist[r, c] while (r, c) != (sr, sc): - found = False for dr, dc in possible_move: nr, nc = r + dr, c + dc if 0 <= nr < rows and 0 <= nc < cols: @@ -62,14 +53,13 @@ def animate_wave_algorithm( r, c = nr, nc d -= 1 path_mask[r, c] = True - found = True break def make_image(frame, show_path=False): h, w = frame.shape img = np.zeros((h, w, 3), dtype=float) img[:, :] = (1.0, 1.0, 1.0) - walls = (maze == 0) + walls = maze == 0 img[walls] = (0.0, 0.0, 0.0) visited = frame >= 0 img[visited] = (0.53, 0.81, 0.98) @@ -130,24 +120,26 @@ def update(frame_id): blit=False, ) - if save_path: anim.save(save_path, writer="pillow") plt.close(fig) return anim + if __name__ == "__main__": # Пример 1 - maze = np.array([ - [0, 0, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 1, 1, 0], - [1, 1, 0, 1, 0, 1, 0], - [0, 0, 1, 1, 0, 1, 0], - [0, 0, 0, 0, 0, 1, 0], - [1, 1, 1, 1, 1, 1, 0], - [0, 0, 0, 0, 0, 0, 0], - ]) + maze = np.array( + [ + [0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 0], + [1, 1, 0, 1, 0, 1, 0], + [0, 0, 1, 1, 0, 1, 0], + [0, 0, 0, 0, 0, 1, 0], + [1, 1, 1, 1, 1, 1, 0], + [0, 0, 0, 0, 0, 0, 0], + ] + ) start = (2, 0) end = (5, 0) @@ -155,9 +147,9 @@ def update(frame_id): animation = animate_wave_algorithm(maze, start, end, save_path) HTML(animation.to_jshtml()) - + # Пример 2 - + maze_path = "./data/maze.npy" loaded_maze = np.load(maze_path) @@ -168,5 +160,3 @@ def update(frame_id): loaded_animation = animate_wave_algorithm(loaded_maze, start, end, loaded_save_path) HTML(loaded_animation.to_jshtml()) - - \ No newline at end of file