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..07d128052 100644 --- a/deprecated_tests/sem01/tests/test_lesson04_tasks.py +++ b/deprecated_tests/sem01/tests/test_lesson04_tasks.py @@ -1,4 +1,4 @@ -import pytest +import pytest import random from solutions.sem01.lesson04.task1 import is_arithmetic_progression @@ -8,62 +8,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 +98,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..f2f6a282b 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,122 @@ 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..0284e8233 100644 --- a/deprecated_tests/sem01/tests/test_lesson08_tasks.py +++ b/deprecated_tests/sem01/tests/test_lesson08_tasks.py @@ -1,10 +1,11 @@ -import pytest +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 +16,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 +29,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 +40,7 @@ def func1() -> None: @collect_statistic(statistics) def func2() -> None: time.sleep(0.1) - + for _ in range(3): func1() @@ -58,10 +61,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 +80,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..16f3d70f1 100644 --- a/deprecated_tests/sem01/tests/test_lesson11_tasks.py +++ b/deprecated_tests/sem01/tests/test_lesson11_tasks.py @@ -84,13 +84,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..6f53b96eb 100644 --- a/deprecated_tests/sem01/tests_hw/test_hw1_tasks.py +++ b/deprecated_tests/sem01/tests_hw/test_hw1_tasks.py @@ -11,7 +11,8 @@ TESTCASE_IDS, ) -NAME_BACKOFF_MODULE = "homeworks.hw1.backoff" # название модуля с backoff +NAME_BACKOFF_MODULE = "homeworks.hw1.backoff" # название модуля с backoff + def test_valid_segments() -> None: """Тест: валидные сегменты.""" @@ -32,53 +33,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 +89,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 +99,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 +110,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 +119,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 +136,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/aggregate_segmentation.py b/homeworks/sem01/hw1/aggregate_segmentation.py index 1cdc176af..332369584 100644 --- a/homeworks/sem01/hw1/aggregate_segmentation.py +++ b/homeworks/sem01/hw1/aggregate_segmentation.py @@ -25,4 +25,48 @@ def aggregate_segmentation( """ # ваш код - return {}, [] + valid: dict[str, dict[str, dict[str, str | float]]] = {} + to_remark: set[str] = set() + seen: dict[tuple[str, str], tuple[float | None, float | None, str | None]] = {} + + def ok_type(v): + return v is None or isinstance(v, str) + + def ok_float(v): + return v is None or isinstance(v, float) + + for seg in segmentation_data: + aid = seg.get("audio_id") + sid = seg.get("segment_id") + if not aid or not sid: + continue + t, st, en = seg.get("type"), seg.get("segment_start"), seg.get("segment_end") + + if not (ok_type(t) and ok_float(st) and ok_float(en)): + to_remark.add(aid) + continue + all_none = t is None and st is None and en is None + any_none = t is None or st is None or en is None + if any_none and not all_none: + to_remark.add(aid) + continue + if t is not None and t not in ALLOWED_TYPES: + to_remark.add(aid) + continue + + key = (aid, sid) + if key in seen and seen[key] != (st, en, t): + to_remark.add(aid) + continue + seen[key] = (st, en, t) + + for (aid, sid), (st, en, t) in seen.items(): + if aid in to_remark or t is None: + continue + valid.setdefault(aid, {})[sid] = {"start": st, "end": en, "type": t} + + for aid in {aid for (aid, _), (_, _, t) in seen.items() if t is None}: + if aid not in to_remark: + valid.setdefault(aid, {}) + + return valid, sorted(to_remark) diff --git a/homeworks/sem01/hw1/backoff.py b/homeworks/sem01/hw1/backoff.py index 696ffa73a..e0abd8db0 100644 --- a/homeworks/sem01/hw1/backoff.py +++ b/homeworks/sem01/hw1/backoff.py @@ -35,4 +35,34 @@ def backoff( """ # ваш код - pass + if not (isinstance(retry_amount, int) and 1 <= retry_amount <= 100): + raise ValueError("retry_amount должен быть целым числом от 1 до 100") + for name, value, lim in ( + ("timeout_start", timeout_start, (0, 10)), + ("timeout_max", timeout_max, (0, 10)), + ("backoff_scale", backoff_scale, (0, 10)), + ): + if not (isinstance(value, (int, float)) and lim[0] < value < lim[1]): + raise ValueError(f"{name} должен быть в интервале {lim}") + + def decorator(func: Callable[P, R]) -> Callable[P, R]: + def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: + delay = timeout_start + last_exc: Exception | None = None + + for attempt in range(retry_amount): + try: + return func(*args, **kwargs) + except backoff_triggers as exc: + last_exc = exc + if attempt == retry_amount - 1: + break + sleep(min(delay, timeout_max) + uniform(0, 0.5)) + delay *= backoff_scale + + assert last_exc is not None + raise last_exc + + return wrapper + + return decorator diff --git a/homeworks/sem01/hw1/cache.py b/homeworks/sem01/hw1/cache.py index 9eb1d5d2d..03f61ddd9 100644 --- a/homeworks/sem01/hw1/cache.py +++ b/homeworks/sem01/hw1/cache.py @@ -24,4 +24,60 @@ def lru_cache(capacity: int) -> Callable[[Callable[P, R]], Callable[P, R]]: ValueError, если после округления capacity - число, меньшее 1. """ # ваш код - pass + try: + capacity = round(capacity) + except Exception: + raise TypeError("capacity must be compatible with round()") + if capacity < 1: + raise ValueError("capacity must be >= 1") + + def decorator(func: Callable[P, R]) -> Callable[P, R]: + class _Node: + __slots__ = ("key", "value", "prev", "next") + + def __init__(self, key: tuple, value: R): + self.key = key + self.value = value + self.prev: _Node | None = None + self.next: _Node | None = None + + head = _Node(None, None) # type: ignore + tail = _Node(None, None) # type: ignore + head.next = tail + tail.prev = head + + cache: dict[tuple, _Node] = {} + + def _remove(node: _Node) -> None: + prev, nxt = node.prev, node.next + prev.next = nxt + nxt.prev = prev + + def _add_to_head(node: _Node) -> None: + node.prev = head + node.next = head.next + head.next.prev = node + head.next = node + + def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: + key = (args, frozenset(kwargs.items())) + if key in cache: + node = cache[key] + _remove(node) + _add_to_head(node) + return node.value + + result = func(*args, **kwargs) + new_node = _Node(key, result) + cache[key] = new_node + _add_to_head(new_node) + + if len(cache) > capacity: + lru = tail.prev + _remove(lru) + del cache[lru.key] + return result + + return wrapper + + return decorator diff --git a/homeworks/sem01/hw1/convert_exception.py b/homeworks/sem01/hw1/convert_exception.py index fe5c770fd..0e7e3b354 100644 --- a/homeworks/sem01/hw1/convert_exception.py +++ b/homeworks/sem01/hw1/convert_exception.py @@ -25,4 +25,18 @@ def convert_exceptions_to_api_compitable_ones( """ # ваш код - pass + def decorator(func: Callable[P, R]) -> Callable[P, R]: + def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: + try: + return func(*args, **kwargs) + except Exception as exc: + api_exc = exception_to_api_exception.get(type(exc)) + if api_exc is not None: + if isinstance(api_exc, Exception): + raise api_exc + raise api_exc() + raise + + return wrapper + + return decorator diff --git a/solutions/sem01/lesson02/task1.py b/solutions/sem01/lesson02/task1.py index c782dcd88..004d02e52 100644 --- a/solutions/sem01/lesson02/task1.py +++ b/solutions/sem01/lesson02/task1.py @@ -1,4 +1,9 @@ def get_factorial(num: int) -> int: factorial = 1 # ваш код + if num == 0: + pass + else: + for i in range(1, num + 1): + factorial *= i return factorial diff --git a/solutions/sem01/lesson02/task2.py b/solutions/sem01/lesson02/task2.py index b91420c56..52caaf20c 100644 --- a/solutions/sem01/lesson02/task2.py +++ b/solutions/sem01/lesson02/task2.py @@ -1,4 +1,10 @@ def get_doubled_factorial(num: int) -> int: factorial = 1 # ваш код + if num <= 1: + factorial = 1 + else: + while num > 1: + factorial *= num + num -= 2 return factorial diff --git a/solutions/sem01/lesson02/task3.py b/solutions/sem01/lesson02/task3.py index ee2a84ecf..e6f392abd 100644 --- a/solutions/sem01/lesson02/task3.py +++ b/solutions/sem01/lesson02/task3.py @@ -1,4 +1,12 @@ def get_amount_of_ways_to_climb(stair_amount: int) -> int: step_prev, step_curr = 1, 1 # ваш код + if stair_amount == 1: + step_curr = 1 + elif stair_amount == 2: + step_curr = 2 + else: + step_prev, step_curr = 1, 2 + for _ in range(3, stair_amount + 1): + step_prev, step_curr = step_curr, step_prev + step_curr return step_curr diff --git a/solutions/sem01/lesson02/task4.py b/solutions/sem01/lesson02/task4.py index 45ff4bb42..ac38ab54f 100644 --- a/solutions/sem01/lesson02/task4.py +++ b/solutions/sem01/lesson02/task4.py @@ -1,4 +1,11 @@ def get_multiplications_amount(num: int) -> int: multiplications_amount = 0 # ваш код + while num > 1: + if num % 2 == 0: + num //= 2 + multiplications_amount += 1 + else: + num = (num - 1) // 2 + multiplications_amount += 2 return multiplications_amount diff --git a/solutions/sem01/lesson02/task5.py b/solutions/sem01/lesson02/task5.py index 8fb9a048d..c221fa3af 100644 --- a/solutions/sem01/lesson02/task5.py +++ b/solutions/sem01/lesson02/task5.py @@ -1,3 +1,5 @@ def get_gcd(num1: int, num2: int) -> int: # ваш код + while num2: + num1, num2 = num2, num1 % num2 return num1 diff --git a/solutions/sem01/lesson02/task6.py b/solutions/sem01/lesson02/task6.py index bec4b6cd9..29f5dfb90 100644 --- a/solutions/sem01/lesson02/task6.py +++ b/solutions/sem01/lesson02/task6.py @@ -1,4 +1,14 @@ def get_sum_of_prime_divisors(num: int) -> int: - sum_of_divisors = 0 # ваш код - return sum_of_divisors + if num < 2: + return 0 + s, f = 0, 2 + while f * f <= num: + if num % f == 0: + s += f + while num % f == 0: + num //= f + f += 1 + if num > 1: + s += num + return s diff --git a/solutions/sem01/lesson02/task7.py b/solutions/sem01/lesson02/task7.py index 4b2d73beb..f1062b4a3 100644 --- a/solutions/sem01/lesson02/task7.py +++ b/solutions/sem01/lesson02/task7.py @@ -2,4 +2,13 @@ def is_palindrome(num: int) -> bool: num_reversed = 0 num_origin = num # ваш код + if num < 0: + num_reversed = abs(num) + else: + num_origin = num + num_reversed = 0 + while num > 0: + digit = num % 10 + num_reversed = num_reversed * 10 + digit + num //= 10 return num_origin == num_reversed diff --git a/solutions/sem01/lesson03/task1.py b/solutions/sem01/lesson03/task1.py index f1d8fe26b..c6604ebd5 100644 --- a/solutions/sem01/lesson03/task1.py +++ b/solutions/sem01/lesson03/task1.py @@ -1,3 +1,7 @@ def flip_bits_in_range(num: int, left_bit: int, right_bit: int) -> int: # ваш код - return num \ No newline at end of file + if left_bit == 0: + left_bit = 1 + cnt = right_bit - left_bit + 1 + mask = ((1 << cnt) - 1) << (left_bit - 1) + return num ^ mask diff --git a/solutions/sem01/lesson03/task2.py b/solutions/sem01/lesson03/task2.py index a3a738c2a..567f36fba 100644 --- a/solutions/sem01/lesson03/task2.py +++ b/solutions/sem01/lesson03/task2.py @@ -1,3 +1,19 @@ def get_cube_root(n: float, eps: float) -> float: # ваш код - return n \ No newline at end of file + sign = -1.0 if n < 0 else 1.0 + a = abs(n) + + power = 0 + while (1 << power) ** 3 < a: + power += 1 + left, right = 0.0, float(1 << power) + + while True: + mid = (left + right) * 0.5 + err = mid * mid * mid - a + if abs(err) <= eps: + return sign * mid + if err < 0: + left = mid + else: + right = mid diff --git a/solutions/sem01/lesson03/task3.py b/solutions/sem01/lesson03/task3.py index 5e91a6ac5..34186dfb7 100644 --- a/solutions/sem01/lesson03/task3.py +++ b/solutions/sem01/lesson03/task3.py @@ -1,3 +1,17 @@ def get_nth_digit(num: int) -> int: # ваш код - return 0 + k = 1 + while True: + first = 10 ** (k - 1) if k > 1 else 0 + count = (10**k - first) // 2 + total = count * k + if num > total: + num -= total + k += 1 + continue + num1 = first + ((num - 1) // k) * 2 + pos = k - 1 - (num - 1) % k + while pos: + num1 //= 10 + pos -= 1 + return num1 % 10 diff --git a/solutions/sem01/lesson04/task1.py b/solutions/sem01/lesson04/task1.py index 47384423a..90d014b61 100644 --- a/solutions/sem01/lesson04/task1.py +++ b/solutions/sem01/lesson04/task1.py @@ -1,3 +1,10 @@ def is_arithmetic_progression(lst: list[list[int]]) -> bool: # ваш код - return False \ No newline at end of file + if len(lst) < 2: + return True + lst = sorted(lst) + diff = lst[1] - lst[0] + for i in range(2, len(lst)): + if lst[i] - lst[i - 1] != diff: + return False + return True diff --git a/solutions/sem01/lesson04/task2.py b/solutions/sem01/lesson04/task2.py index 4591d0a3e..4a54075a8 100644 --- a/solutions/sem01/lesson04/task2.py +++ b/solutions/sem01/lesson04/task2.py @@ -1,3 +1,19 @@ def merge_intervals(intervals: list[list[int, int]]) -> list[list[int, int]]: # ваш код - return [[0,0]] \ No newline at end of file + if not intervals: + return [] + + intervals.sort(key=lambda x: x[0]) + + merged = [] + current_start, current_end = intervals[0] + + for start, end in intervals[1:]: + if start < current_end + 1: + current_end = max(current_end, end) + else: + merged.append([current_start, current_end]) + current_start, current_end = start, end + + merged.append([current_start, current_end]) + return merged diff --git a/solutions/sem01/lesson04/task3.py b/solutions/sem01/lesson04/task3.py index 7253f6cbd..5b360393a 100644 --- a/solutions/sem01/lesson04/task3.py +++ b/solutions/sem01/lesson04/task3.py @@ -1,3 +1,7 @@ def find_single_number(nums: list[int]) -> int: # ваш код - return 0 + nums = sorted(nums) + unique = 0 + for num in nums: + unique ^= num + return unique diff --git a/solutions/sem01/lesson04/task4.py b/solutions/sem01/lesson04/task4.py index b21bc5a39..d2ad55f4d 100644 --- a/solutions/sem01/lesson04/task4.py +++ b/solutions/sem01/lesson04/task4.py @@ -1,3 +1,12 @@ def move_zeros_to_end(nums: list[int]) -> list[int]: # ваш код - return 0 \ No newline at end of file + insert_pos = 0 + for v in nums: + if v != 0: + nums[insert_pos] = v + insert_pos += 1 + + for i in range(insert_pos, len(nums)): + nums[i] = 0 + + return insert_pos diff --git a/solutions/sem01/lesson04/task5.py b/solutions/sem01/lesson04/task5.py index 02d7742bb..2658669ca 100644 --- a/solutions/sem01/lesson04/task5.py +++ b/solutions/sem01/lesson04/task5.py @@ -1,3 +1,26 @@ def find_row_with_most_ones(matrix: list[list[int]]) -> int: # ваш код - return 0 \ No newline at end of file + if not matrix or not matrix[0]: + return 0 + + n, m = len(matrix), len(matrix[0]) + best_row = 0 + best_ones = 0 + + def first_one(row: list[int]) -> int: + lo, hi = 0, m + while lo < hi: + mid = (lo + hi) // 2 + if row[mid] == 1: + hi = mid + else: + lo = mid + 1 + return lo + + for row_idx in range(n): + ones = m - first_one(matrix[row_idx]) + if ones > best_ones: + best_ones = ones + best_row = row_idx + + return best_row diff --git a/solutions/sem01/lesson04/task6.py b/solutions/sem01/lesson04/task6.py index 16df27ca6..174646522 100644 --- a/solutions/sem01/lesson04/task6.py +++ b/solutions/sem01/lesson04/task6.py @@ -1,3 +1,15 @@ -def count_cycles(arr: list[int]) -> int: +def count_cycles(arr: list[int]) -> int: # ваш код - return 0 \ No newline at end of file + n = len(arr) + cycles = 0 + + for i in range(n): + if arr[i] >= 0: + cycles += 1 + idx = i + while arr[idx] >= 0: + next_idx = arr[idx] + arr[idx] = -arr[idx] - 1 + idx = next_idx + + return cycles diff --git a/solutions/sem01/lesson05/task1.py b/solutions/sem01/lesson05/task1.py index 9a17211e5..f8b8221ee 100644 --- a/solutions/sem01/lesson05/task1.py +++ b/solutions/sem01/lesson05/task1.py @@ -1,3 +1,6 @@ def is_palindrome(text: str) -> bool: # ваш код - return False \ No newline at end of file + text = text.lower() + if text == text[::-1]: + return True + return False diff --git a/solutions/sem01/lesson05/task2.py b/solutions/sem01/lesson05/task2.py index 367503802..15bcb833c 100644 --- a/solutions/sem01/lesson05/task2.py +++ b/solutions/sem01/lesson05/task2.py @@ -1,3 +1,7 @@ def are_anagrams(word1: str, word2: str) -> bool: # ваш код - return False \ No newline at end of file + word1 = sorted(word1) + word2 = sorted(word2) + if word2 == word1: + return True + return False diff --git a/solutions/sem01/lesson05/task3.py b/solutions/sem01/lesson05/task3.py index e368e2f49..66040b78f 100644 --- a/solutions/sem01/lesson05/task3.py +++ b/solutions/sem01/lesson05/task3.py @@ -1,3 +1,9 @@ def is_punctuation(text: str) -> bool: # ваш код - return False + punctuation = """!"#$%&'()*+,-./:;<=>?@[\\]^{|}~`""" + if not text: + return False + for ch in text: + if ch not in punctuation: + return False + return True diff --git a/solutions/sem01/lesson05/task4.py b/solutions/sem01/lesson05/task4.py index 4c4e9086e..98f6a71ab 100644 --- a/solutions/sem01/lesson05/task4.py +++ b/solutions/sem01/lesson05/task4.py @@ -1,3 +1,15 @@ def unzip(compress_text: str) -> str: # ваш код - return compress_text \ No newline at end of file + parts = compress_text.split() + result = [] + for part in parts: + if "*" in part: + base, num_str = part.split("*", 1) + try: + n = int(num_str) + except ValueError: + n = 0 + result.append(base * n) + else: + result.append(part) + return "".join(result) diff --git a/solutions/sem01/lesson05/task5.py b/solutions/sem01/lesson05/task5.py index 076c5bb6c..cc4947b64 100644 --- a/solutions/sem01/lesson05/task5.py +++ b/solutions/sem01/lesson05/task5.py @@ -1,3 +1,31 @@ -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 + pos = 0 + text_len = len(text) + + for token in reg_expr: + if pos >= text_len: + return False + ch = text[pos] + + if token == "d": + if not ch.isdigit(): + return False + while pos < text_len and text[pos].isdigit(): + pos += 1 + elif token == "w": + if not ch.isalpha(): + return False + while pos < text_len and text[pos].isalpha(): + pos += 1 + elif token == "s": + if not (ch.isalpha() or ch.isdigit()): + return False + while pos < text_len and (text[pos].isalpha() or text[pos].isdigit()): + pos += 1 + else: + if ch != token: + return False + pos += 1 + + return pos == text_len diff --git a/solutions/sem01/lesson05/task6.py b/solutions/sem01/lesson05/task6.py index 1b914ada7..1f7381d7d 100644 --- a/solutions/sem01/lesson05/task6.py +++ b/solutions/sem01/lesson05/task6.py @@ -1,3 +1,17 @@ def simplify_path(path: str) -> str: # ваш код - return path \ No newline at end of file + parts = path.split("/") + stack = [] + + for part in parts: + if part == "" or part == ".": + continue + if part == "..": + if stack: + stack.pop() + else: + return "" + else: + stack.append(part) + + return "/" + "/".join(stack) diff --git a/solutions/sem01/lesson06/task1.py b/solutions/sem01/lesson06/task1.py index 2d1e30e96..2ff794eb5 100644 --- a/solutions/sem01/lesson06/task1.py +++ b/solutions/sem01/lesson06/task1.py @@ -1,3 +1,22 @@ def int_to_roman(num: int) -> str: # ваш код - return "" \ No newline at end of file + d = { + 1000: "M", + 900: "CM", + 500: "D", + 400: "CD", + 100: "C", + 90: "XC", + 50: "L", + 40: "XL", + 10: "X", + 9: "IX", + 5: "V", + 4: "IV", + 1: "I", + } + r = [] + for v, s in d.items(): + q, num = divmod(num, v) + r.append(s * q) + return "".join(r) diff --git a/solutions/sem01/lesson06/task2.py b/solutions/sem01/lesson06/task2.py index f535b5a0c..ccc2c593d 100644 --- a/solutions/sem01/lesson06/task2.py +++ b/solutions/sem01/lesson06/task2.py @@ -1,3 +1,11 @@ def get_len_of_longest_substring(text: str) -> int: # ваш код - return 0 \ No newline at end of file + start = 0 + seen = {} + best = 0 + for i, ch in enumerate(text): + if ch in seen and seen[ch] >= start: + start = seen[ch] + 1 + seen[ch] = i + best = max(best, i - start + 1) + return best diff --git a/solutions/sem01/lesson06/task3.py b/solutions/sem01/lesson06/task3.py index 7449a1e72..2c487963b 100644 --- a/solutions/sem01/lesson06/task3.py +++ b/solutions/sem01/lesson06/task3.py @@ -2,6 +2,14 @@ def is_there_any_good_subarray( nums: list[int], k: int, ) -> bool: - # ваш код + prefs = {0: -1} + s = 0 + for i, v in enumerate(nums): + s = (s + v) % k + if s in prefs: + if i - prefs[s] >= 2: + return True + else: + prefs[s] = i return False diff --git a/solutions/sem01/lesson06/task4.py b/solutions/sem01/lesson06/task4.py index 5b75a110c..6b4ad46a0 100644 --- a/solutions/sem01/lesson06/task4.py +++ b/solutions/sem01/lesson06/task4.py @@ -1,3 +1,8 @@ def count_unique_words(text: str) -> int: # ваш код - return 0 \ No newline at end of file + seen = set() + for raw in text.split(): + word = raw.strip("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~").lower() + if word: + seen.add(word) + return len(seen) diff --git a/solutions/sem01/lesson08/task1.py b/solutions/sem01/lesson08/task1.py index 4390f6c84..ed3deaf43 100644 --- a/solutions/sem01/lesson08/task1.py +++ b/solutions/sem01/lesson08/task1.py @@ -1,5 +1,13 @@ from typing import Callable + def make_averager(accumulation_period: int) -> Callable[[float], float]: - # ваш код - pass \ No newline at end of file + values = [] + + def get_avg(value: float) -> float: + values.append(value) + if len(values) > accumulation_period: + values.pop(0) + return sum(values) / len(values) + + return get_avg diff --git a/solutions/sem01/lesson08/task2.py b/solutions/sem01/lesson08/task2.py index 6e4af8707..6f3ddc1d9 100644 --- a/solutions/sem01/lesson08/task2.py +++ b/solutions/sem01/lesson08/task2.py @@ -1,10 +1,30 @@ +from functools import wraps +from time import time from typing import Callable, TypeVar 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 + def decorator(func: T) -> T: + @wraps(func) + def wrapper(*args, **kwargs): + start = time() + try: + return func(*args, **kwargs) + finally: + elapsed = time() - start + name = func.__name__ + if name not in statistics: + statistics[name] = [0.0, 0] + old_avg, old_cnt = statistics[name] + new_cnt = old_cnt + 1 + new_avg = old_avg + (elapsed - old_avg) / new_cnt + statistics[name][0] = new_avg + statistics[name][1] = new_cnt + + return wrapper + + return decorator + pass diff --git a/solutions/sem01/lesson11/task1.py b/solutions/sem01/lesson11/task1.py index 6a15f31fb..504d80b60 100644 --- a/solutions/sem01/lesson11/task1.py +++ b/solutions/sem01/lesson11/task1.py @@ -1,10 +1,125 @@ +# ваш код +import math + + class Vector2D: - def conj(self) -> "Vector2D": - # ваш код - return Vector2D() + __slots__ = ("_abscissa", "_ordinate") + + def __init__(self, abscissa: float = 0.0, ordinate: float = 0.0) -> None: + self._abscissa = float(abscissa) + self._ordinate = float(ordinate) + + @property + def abscissa(self) -> float: + return self._abscissa + + @property + def ordinate(self) -> float: + return self._ordinate + + def __repr__(self) -> str: + a, o = self.abscissa, self.ordinate + a_str = str(int(a)) if a.is_integer() else str(a) + o_str = str(int(o)) if o.is_integer() else str(o) + return f"Vector2D(abscissa={a_str}, ordinate={o_str})" + + @staticmethod + def _snap(f: float) -> float: + return round(f, 12) + + def __eq__(self, other): + if not isinstance(other, Vector2D): + return NotImplemented + return self._snap(self.abscissa) == self._snap(other.abscissa) and self._snap( + self.ordinate + ) == self._snap(other.ordinate) + + def __ne__(self, other): + return not self == other + + def __lt__(self, other): + if not isinstance(other, Vector2D): + return NotImplemented + a1, o1 = self._snap(self.abscissa), self._snap(self.ordinate) + a2, o2 = self._snap(other.abscissa), self._snap(other.ordinate) + return (a1 < a2) or (a1 == a2 and o1 < o2) + + def __le__(self, other): + return self < other or self == other + + def __gt__(self, other): + return not (self <= other) + + def __ge__(self, other): + return not (self < other) + + def __abs__(self) -> float: + return math.hypot(self.abscissa, self.ordinate) + + def __bool__(self) -> bool: + return abs(self) > 1e-12 + + def __mul__(self, other): + if isinstance(other, (int, float)): + return Vector2D(self.abscissa * other, self.ordinate * other) + return NotImplemented + + __rmul__ = __mul__ + + def __truediv__(self, other): + if isinstance(other, (int, float)): + return Vector2D(self.abscissa / other, self.ordinate / other) + return NotImplemented + + def __rtruediv__(self, other): + raise TypeError( + f"unsupported operand type(s) for /: '{type(other).__name__}' and 'Vector2D'" + ) + + def __add__(self, other): + if isinstance(other, (int, float)): + return Vector2D(self.abscissa + other, self.ordinate + other) + if isinstance(other, Vector2D): + return Vector2D(self.abscissa + other.abscissa, self.ordinate + other.ordinate) + return NotImplemented + + __radd__ = __add__ + + def __sub__(self, other): + if isinstance(other, (int, float)): + return Vector2D(self.abscissa - other, self.ordinate - other) + if isinstance(other, Vector2D): + return Vector2D(self.abscissa - other.abscissa, self.ordinate - other.ordinate) + return NotImplemented + + def __rsub__(self, other): + raise TypeError( + f"unsupported operand type(s) for -: '{type(other).__name__}' and 'Vector2D'" + ) + + def __neg__(self): + return Vector2D(-self.abscissa, -self.ordinate) + + def __complex__(self): + return complex(self.abscissa, self.ordinate) + + def __float__(self): + return float(abs(self)) + + def __int__(self): + return int(abs(self)) + + def __matmul__(self, other): + if not isinstance(other, Vector2D): + return NotImplemented + return self.abscissa * other.abscissa + self.ordinate * other.ordinate def get_angle(self, other: "Vector2D") -> float: - # ваш код - return 0 + if not isinstance(other, Vector2D): + raise TypeError("other must be Vector2D") + if not self or not other: + raise ValueError("Cannot calculate angle between zero vector") + return math.acos((self @ other) / (abs(self) * abs(other))) - # ваш код + def conj(self) -> "Vector2D": + return Vector2D(self.abscissa, -self.ordinate) diff --git a/solutions/sem01/lesson12/task1.py b/solutions/sem01/lesson12/task1.py index d1bb828c1..b7dafb5d9 100644 --- a/solutions/sem01/lesson12/task1.py +++ b/solutions/sem01/lesson12/task1.py @@ -3,4 +3,14 @@ def chunked(iterable: Iterable, size: int) -> Generator[tuple[Any], None, None]: # ваш код - ... + chunk = [] + append_to_chunk = chunk.append + for item in iterable: + append_to_chunk(item) + if len(chunk) == size: + yield tuple(chunk) + chunk = [] + append_to_chunk = chunk.append + + if chunk: + yield tuple(chunk) diff --git a/solutions/sem01/lesson12/task2.py b/solutions/sem01/lesson12/task2.py index 3ad802ee7..2b299d2a9 100644 --- a/solutions/sem01/lesson12/task2.py +++ b/solutions/sem01/lesson12/task2.py @@ -3,4 +3,12 @@ def circle(iterable: Iterable) -> Generator[Any, None, None]: # ваш код - ... + saved = [] + + for element in iterable: + yield element + saved.append(element) + if not saved: + return + while True: + yield from saved diff --git a/solutions/sem01/lesson12/task3.py b/solutions/sem01/lesson12/task3.py index 64c112ccc..8662e9ff9 100644 --- a/solutions/sem01/lesson12/task3.py +++ b/solutions/sem01/lesson12/task3.py @@ -7,6 +7,20 @@ def __init__( path_to_file: str, ) -> None: # ваш код - ... + self.path_to_file = path_to_file + self.original_stdout = None + self.file = None # ваш код + + def __enter__(self): + self._file = open(self.path_to_file, "w") + + self._stdout = sys.stdout + sys.stdout = self._file + return self + + def __exit__(self, *args, **kwargs): + sys.stdout = self._stdout + self._file.close() + return False diff --git a/solutions/sem02/lesson03/task1.py b/solutions/sem02/lesson03/task1.py index 2c3fc0b58..c1226f2aa 100644 --- a/solutions/sem02/lesson03/task1.py +++ b/solutions/sem02/lesson03/task1.py @@ -8,13 +8,26 @@ class ShapeMismatchError(Exception): def sum_arrays_vectorized( lhs: np.ndarray, rhs: np.ndarray, -) -> np.ndarray: ... +) -> np.ndarray: + if lhs.shape != rhs.shape: + raise ShapeMismatchError + return lhs + rhs -def compute_poly_vectorized(abscissa: np.ndarray) -> np.ndarray: ... + +def compute_poly_vectorized(abscissa: np.ndarray) -> np.ndarray: + return 3 * abscissa**2 + 2 * abscissa + 1 def get_mutual_l2_distances_vectorized( lhs: np.ndarray, rhs: np.ndarray, -) -> np.ndarray: ... +) -> np.ndarray: + if lhs.shape[1] != rhs.shape[1]: + raise ShapeMismatchError + + lhs_squared = np.sum(lhs**2, axis=1).reshape(-1, 1) + rhs_squared = np.sum(rhs**2, axis=1).reshape(1, -1) + cross_term = np.dot(lhs, rhs.T) + + return np.sqrt(lhs_squared - 2 * cross_term + rhs_squared) diff --git a/solutions/sem02/lesson03/task2.py b/solutions/sem02/lesson03/task2.py index fc823c1d6..c9f102dc0 100644 --- a/solutions/sem02/lesson03/task2.py +++ b/solutions/sem02/lesson03/task2.py @@ -9,11 +9,31 @@ def convert_from_sphere( distances: np.ndarray, azimuth: np.ndarray, inclination: np.ndarray, -) -> tuple[np.ndarray, np.ndarray, np.ndarray]: ... +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + if not (distances.shape == azimuth.shape == inclination.shape): + raise ShapeMismatchError + + x = distances * np.sin(inclination) * np.cos(azimuth) + y = distances * np.sin(inclination) * np.sin(azimuth) + z = distances * np.cos(inclination) + + return x, y, z def convert_to_sphere( abscissa: np.ndarray, ordinates: np.ndarray, applicates: np.ndarray, -) -> tuple[np.ndarray, np.ndarray, np.ndarray]: ... +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + if not (abscissa.shape == ordinates.shape == applicates.shape): + raise ShapeMismatchError + + r = np.sqrt(abscissa**2 + ordinates**2 + applicates**2) + + phi = np.arctan2(ordinates, abscissa) + + theta = np.zeros(r.shape) + mask = r != 0 + theta[mask] = np.arccos(np.clip(applicates[mask] / r[mask], -1, 1)) + + return r, phi, theta diff --git a/solutions/sem02/lesson03/task3.py b/solutions/sem02/lesson03/task3.py index 477acd0ce..8becadb6f 100644 --- a/solutions/sem02/lesson03/task3.py +++ b/solutions/sem02/lesson03/task3.py @@ -3,4 +3,18 @@ def get_extremum_indices( ordinates: np.ndarray, -) -> tuple[np.ndarray, np.ndarray]: ... +) -> tuple[np.ndarray, np.ndarray]: + if ordinates.size < 3: + raise ValueError + + left = ordinates[:-2] + center = ordinates[1:-1] + right = ordinates[2:] + + min_mask = (center < left) & (center < right) + max_mask = (center > left) & (center > right) + + mins = np.where(min_mask)[0] + 1 + maxs = np.where(max_mask)[0] + 1 + + return mins, maxs diff --git a/solutions/sem02/lesson04/task1.py b/solutions/sem02/lesson04/task1.py index 1b5526c1f..cd31dac8a 100644 --- a/solutions/sem02/lesson04/task1.py +++ b/solutions/sem02/lesson04/task1.py @@ -3,7 +3,21 @@ def pad_image(image: np.ndarray, pad_size: int) -> np.ndarray: # ваш код - return image + if pad_size < 1: + raise ValueError + + if image.ndim == 2: + h, w = image.shape + padded_image = np.zeros((h + 2 * pad_size, w + 2 * pad_size), dtype=image.dtype) + padded_image[pad_size : pad_size + h, pad_size : pad_size + w] = image + elif image.ndim == 3: + h, w, c = image.shape + padded_image = np.zeros((h + 2 * pad_size, w + 2 * pad_size, c), dtype=image.dtype) + padded_image[pad_size : pad_size + h, pad_size : pad_size + w, :] = image + else: + raise ValueError + + return padded_image def blur_image( @@ -11,7 +25,44 @@ def blur_image( kernel_size: int, ) -> np.ndarray: # ваш код - return image + if kernel_size < 1 or kernel_size % 2 == 0: + raise ValueError + + if kernel_size == 1: + return image.copy() + + pad_size = kernel_size // 2 + window_area = kernel_size * kernel_size + + if image.ndim == 2: + h, w = image.shape + padded = pad_image(image, pad_size).astype(np.uint64, copy=False) + integral = np.zeros((padded.shape[0] + 1, padded.shape[1] + 1), dtype=np.uint64) + integral[1:, 1:] = np.cumsum(np.cumsum(padded, axis=0), axis=1) + + window_sums = ( + integral[kernel_size : kernel_size + h, kernel_size : kernel_size + w] + - integral[:h, kernel_size : kernel_size + w] + - integral[kernel_size : kernel_size + h, :w] + + integral[:h, :w] + ) + return (window_sums // window_area).astype(np.uint8) + + if image.ndim == 3: + h, w, c = image.shape + padded = pad_image(image, pad_size).astype(np.uint64, copy=False) + integral = np.zeros((padded.shape[0] + 1, padded.shape[1] + 1, c), dtype=np.uint64) + integral[1:, 1:, :] = np.cumsum(np.cumsum(padded, axis=0), axis=1) + + window_sums = ( + integral[kernel_size : kernel_size + h, kernel_size : kernel_size + w, :] + - integral[:h, kernel_size : kernel_size + w, :] + - integral[kernel_size : kernel_size + h, :w, :] + + integral[:h, :w, :] + ) + return (window_sums // window_area).astype(np.uint8) + + raise ValueError if __name__ == "__main__": diff --git a/solutions/sem02/lesson04/task2.py b/solutions/sem02/lesson04/task2.py index be9a2288f..3a94d5a76 100644 --- a/solutions/sem02/lesson04/task2.py +++ b/solutions/sem02/lesson04/task2.py @@ -6,5 +6,27 @@ def get_dominant_color_info( threshold: int = 5, ) -> tuple[np.uint8, float]: # ваш код + if threshold < 1: + raise ValueError("threshold must be positive") - return 0, 0 + pixels = image.flatten() + + value_counts = np.bincount(pixels, minlength=256).astype(np.int64) + color_indices = np.arange(256, dtype=np.int32).reshape(-1, 1) + + is_present = value_counts > 0 + + pairwise_diffs = np.abs(color_indices - color_indices.T) + within_threshold = pairwise_diffs < threshold + + neighborhood_sums = np.sum(within_threshold * value_counts, axis=1) + + ranking_scores = neighborhood_sums * (image.size + 1) + value_counts + ranking_scores[~is_present] = -1 + + dominant_idx = np.argmax(ranking_scores) + + dominant_color = np.uint8(dominant_idx) + percentage = float(neighborhood_sums[dominant_idx] / image.size) + + return dominant_color, percentage diff --git a/solutions/sem02/lesson05/task1.py b/solutions/sem02/lesson05/task1.py index e9c7c3c56..8e1d662b1 100644 --- a/solutions/sem02/lesson05/task1.py +++ b/solutions/sem02/lesson05/task1.py @@ -9,4 +9,19 @@ def can_satisfy_demand( costs: np.ndarray, resource_amounts: np.ndarray, demand_expected: np.ndarray, -) -> bool: ... +) -> bool: + costs = np.array(costs) + resource_amounts = np.array(resource_amounts) + demand_expected = np.array(demand_expected) + + if costs.ndim != 2 or resource_amounts.ndim != 1 or demand_expected.ndim != 1: + raise ShapeMismatchError() + + M, N = costs.shape + + if resource_amounts.shape[0] != M or demand_expected.shape[0] != N: + raise ShapeMismatchError() + + total_resource_needed = costs @ demand_expected + + return np.all(total_resource_needed <= resource_amounts) diff --git a/solutions/sem02/lesson05/task2.py b/solutions/sem02/lesson05/task2.py index be1fb9d2b..a47053c6a 100644 --- a/solutions/sem02/lesson05/task2.py +++ b/solutions/sem02/lesson05/task2.py @@ -8,4 +8,30 @@ 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]: + matrix = np.array(matrix) + vector = np.array(vector) + + if matrix.ndim != 2 or vector.ndim != 1: + raise ShapeMismatchError() + + if matrix.shape[0] != matrix.shape[1]: + raise ShapeMismatchError() + + N = matrix.shape[0] + + if vector.shape[0] != N: + raise ShapeMismatchError() + + rank = np.linalg.matrix_rank(matrix) + if rank < N: + return (None, None) + + dots = matrix @ vector + norm_sq = np.sum(matrix * matrix, axis=1) + proj_coeffs = dots / norm_sq + + projections = proj_coeffs[:, None] * matrix + components = vector - projections + + return (projections, components) diff --git a/solutions/sem02/lesson05/task3.py b/solutions/sem02/lesson05/task3.py index 0c66906cb..0c60c6e2c 100644 --- a/solutions/sem02/lesson05/task3.py +++ b/solutions/sem02/lesson05/task3.py @@ -9,4 +9,27 @@ def adaptive_filter( Vs: np.ndarray, Vj: np.ndarray, diag_A: np.ndarray, -) -> np.ndarray: ... +) -> np.ndarray: + Vs = np.array(Vs) + Vj = np.array(Vj) + diag_A = np.array(diag_A) + + if Vs.ndim != 2 or Vj.ndim != 2 or diag_A.ndim != 1: + raise ShapeMismatchError() + + M, N = Vs.shape + M_vj, K = Vj.shape + + if M_vj != M or diag_A.shape[0] != K: + raise ShapeMismatchError() + + I_K = np.eye(K) + + Vj_H = Vj.conj().T + + gram = Vj_H @ Vj + inner = I_K + gram * diag_A + rhs = Vj_H @ Vs + solution = np.linalg.solve(inner, rhs) + + return Vs - Vj @ solution diff --git a/solutions/sem02/lesson07/task1.py b/solutions/sem02/lesson07/task1.py index 3a505d89b..3d600843e 100644 --- a/solutions/sem02/lesson07/task1.py +++ b/solutions/sem02/lesson07/task1.py @@ -14,7 +14,73 @@ def visualize_diagrams( diagram_type: Any, ) -> None: # ваш код - pass + if abscissa.shape != ordinates.shape: + raise ShapeMismatchError() + + valid_types = ["hist", "violin", "box"] + if diagram_type not in valid_types: + raise ValueError() + + figure = plt.figure(figsize=(8, 8)) + space = 0.2 + 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="cornflowerblue", alpha=0.5) + + if diagram_type == "hist": + axis_hor.hist(abscissa, bins=50, color="cornflowerblue", density=True, alpha=0.5) + axis_vert.hist( + ordinates, + bins=50, + color="cornflowerblue", + orientation="horizontal", + density=True, + alpha=0.5, + ) + axis_hor.invert_yaxis() + axis_vert.invert_xaxis() + + elif diagram_type == "box": + box_props = dict(facecolor="lightsteelblue") + median_props = dict(color="k") + axis_hor.boxplot( + abscissa, + vert=False, + patch_artist=True, + boxprops=box_props, + medianprops=median_props, + ) + axis_vert.boxplot( + ordinates, + vert=True, + patch_artist=True, + boxprops=box_props, + medianprops=median_props, + ) + axis_hor.set_yticks([]) + axis_vert.set_xticks([]) + + elif diagram_type == "violin": + parts_hor = axis_hor.violinplot(abscissa, vert=False, showmedians=True) + parts_vert = axis_vert.violinplot(ordinates, vert=True, showmedians=True) + for parts in (parts_hor, parts_vert): + for body in parts["bodies"]: + body.set_facecolor("cornflowerblue") + body.set_edgecolor("blue") + for key in parts: + if key != "bodies": + parts[key].set_edgecolor("cornflowerblue") + axis_hor.set_yticks([]) + axis_vert.set_xticks([]) + + axis_hor.invert_yaxis() + axis_vert.invert_xaxis() + axis_vert.tick_params(axis="y", labelleft=False) + axis_hor.tick_params(axis="x", labelbottom=False) if __name__ == "__main__": diff --git a/solutions/sem02/lesson07/task2.py b/solutions/sem02/lesson07/task2.py index decd607ef..69a819c47 100644 --- a/solutions/sem02/lesson07/task2.py +++ b/solutions/sem02/lesson07/task2.py @@ -1 +1,111 @@ # ваш код (используйте функции или классы для решения данной задачи) +import json +import os + +import matplotlib.pyplot as plt +import numpy as np + + +def get_data_path() -> str: + current_dir = os.path.dirname(os.path.abspath(__file__)) + return os.path.join(current_dir, "data", "medic_data.json") + + +def get_output_path() -> str: + current_dir = os.path.dirname(os.path.abspath(__file__)) + output_dir = os.path.join(current_dir, "output") + os.makedirs(output_dir, exist_ok=True) + return os.path.join(output_dir, "task2_result.png") + + +def load_data(filepath: str) -> dict: + with open(filepath, "r") as file: + return json.load(file) + + +def count_stages(data: list) -> dict: + counts = {"I": 0, "II": 0, "III": 0, "IV": 0} + for item in data: + if item in counts: + counts[item] += 1 + return counts + + +def prepare_data(data_dict: dict) -> tuple: + before_counts = count_stages(data_dict["before"]) + after_counts = count_stages(data_dict["after"]) + + stages = ["I", "II", "III", "IV"] + before_values = [before_counts[s] for s in stages] + after_values = [after_counts[s] for s in stages] + + return stages, before_values, after_values + + +def create_visualization(stages: list, before: list, after: list, output_path: str) -> None: + figure, axis = plt.subplots(figsize=(16, 9)) + + x = np.arange(len(stages)) + width = 0.35 + + axis.bar( + x - width / 2, + before, + width, + label="before", + color="mediumseagreen", + edgecolor="seagreen", + ) + + axis.bar( + x + width / 2, + after, + width, + label="after", + color="mediumpurple", + edgecolor="rebeccapurple", + ) + + axis.set_ylabel("amount of people", fontsize=18, fontweight="bold", c="dimgray") + axis.set_title( + "Mitral disease stages", + fontsize=24, + fontweight="bold", + c="dimgray", + ) + axis.set_xticks(x, labels=stages, weight="bold") + axis.tick_params(axis="x", labelsize=18, labelcolor="dimgray") + axis.tick_params(axis="y", labelsize=14, labelcolor="dimgray") + axis.legend(fontsize=20) + + plt.tight_layout() + plt.savefig(output_path, dpi=150, bbox_inches="tight") + plt.show() + + +def analyze_results(before: list, after: list) -> str: + severe_before = before[2] + before[3] + severe_after = after[2] + after[3] + + if severe_after < severe_before: + return "Имплант эффективен: доля тяжелых стадий (III-IV) снизилась." + elif severe_after > severe_before: + return "Имплант неэффективен: доля тяжелых стадий (III-IV) выросла." + else: + return "Эффект импланта не выражен: доля тяжелых стадий не изменилась." + + +def main(): + data_path = get_data_path() + output_path = get_output_path() + + data = load_data(data_path) + stages, before, after = prepare_data(data) + create_visualization(stages, before, after, output_path) + + conclusion = analyze_results(before, after) + print(conclusion) + + +if __name__ == "__main__": + main() diff --git a/solutions/sem02/lesson08/task1.py b/solutions/sem02/lesson08/task1.py index 89f88572f..aadc7ff7c 100644 --- a/solutions/sem02/lesson08/task1.py +++ b/solutions/sem02/lesson08/task1.py @@ -1,5 +1,3 @@ -from functools import partial - import matplotlib.pyplot as plt import numpy as np @@ -7,29 +5,80 @@ from matplotlib.animation import FuncAnimation +def _build_signal( + time_values: np.ndarray, + modulation, + carrier_frequency: float, +) -> np.ndarray: + if modulation is None: + modulation_values = np.ones_like(time_values) + else: + try: + modulation_values = modulation(time_values) + except TypeError: + modulation_values = np.vectorize(modulation)(time_values) + + modulation_values = np.asarray(modulation_values, dtype=float) + return modulation_values * np.sin(2 * np.pi * carrier_frequency * time_values) + + 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: # ваш код - return FuncAnimation() + local_time = np.arange(0, plot_duration, time_step) + + max_time = plot_duration + max(num_frames - 1, 0) * animation_step + full_time = np.arange(0, max_time + time_step, time_step) + full_signal = _build_signal(full_time, modulation, fc) + + max_amplitude = float(np.max(np.abs(full_signal))) + if max_amplitude == 0: + max_amplitude = 1.0 + + figure, axis = plt.subplots(figsize=(10, 4)) + axis: plt.Axes + + axis.set_xlim(0, plot_duration) + axis.set_ylim(-1.2 * max_amplitude, 1.2 * max_amplitude) + axis.set_title("Амплитудно-модулированный сигнал") + axis.set_xlabel("Время, с") + axis.set_ylabel("Амплитуда") + axis.grid(True, alpha=0.3) + + (line,) = axis.plot([], [], linewidth=2) + + def update(frame_id: int): + current_time = local_time + frame_id * animation_step + signal_values = _build_signal(current_time, modulation, fc) + line.set_data(local_time, signal_values) + return [line] + + animation = FuncAnimation( + figure, + update, + frames=num_frames, + interval=50, + blit=True, + ) + + if save_path: + animation.save(save_path, writer="pillow", fps=24) + + return animation 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 - save_path_with_modulation = "modulated_signal.gif" + num_frames = 100 + plot_duration = np.pi / 2 + time_step = 0.001 + animation_step = np.pi / 200 + fc = 50 + save_path_with_modulation = "solutions/sem02/lesson08/modulated_signal.gif" animation = create_modulation_animation( modulation=modulation_function, @@ -38,6 +87,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 b677c0702..ae3edd3a0 100644 --- a/solutions/sem02/lesson08/task2.py +++ b/solutions/sem02/lesson08/task2.py @@ -1,4 +1,4 @@ -from functools import partial +from pathlib import Path import matplotlib.pyplot as plt import numpy as np @@ -7,47 +7,210 @@ from matplotlib.animation import FuncAnimation +def _is_inside(maze: np.ndarray, row: int, col: int) -> bool: + return 0 <= row < maze.shape[0] and 0 <= col < maze.shape[1] + + +def _neighbors(row: int, col: int) -> list[tuple[int, int]]: + return [ + (row - 1, col), + (row + 1, col), + (row, col - 1), + (row, col + 1), + ] + + +def _run_wave_algorithm( + maze: np.ndarray, + start: tuple[int, int], + end: tuple[int, int], +) -> tuple[np.ndarray, list[list[tuple[int, int]]], list[tuple[int, int]]]: + distances = np.full(maze.shape, -1, dtype=int) + + start_row, start_col = start + end_row, end_col = end + + distances[start_row, start_col] = 0 + wave_layers = [[start]] + current_layer = [start] + + found = start == end + distance_value = 0 + + while current_layer and not found: + next_layer = [] + distance_value += 1 + + for row, col in current_layer: + for next_row, next_col in _neighbors(row, col): + if not _is_inside(maze, next_row, next_col): + continue + if maze[next_row, next_col] == 0: + continue + if distances[next_row, next_col] != -1: + continue + + distances[next_row, next_col] = distance_value + next_layer.append((next_row, next_col)) + + if (next_row, next_col) == end: + found = True + + if next_layer: + wave_layers.append(next_layer) + current_layer = next_layer + + path: list[tuple[int, int]] = [] + if distances[end_row, end_col] != -1: + current = end + path = [current] + + while current != start: + row, col = current + for next_row, next_col in _neighbors(row, col): + if not _is_inside(maze, next_row, next_col): + continue + if distances[next_row, next_col] == distances[row, col] - 1: + current = (next_row, next_col) + path.append(current) + break + + path.reverse() + + return distances, wave_layers, path + + +def _build_animation_frames( + maze: np.ndarray, + start: tuple[int, int], + end: tuple[int, int], + wave_layers: list[list[tuple[int, int]]], + path: list[tuple[int, int]], +) -> list[np.ndarray]: + state = np.where(maze == 0, 0, 1).astype(int) + frames = [state.copy()] + + state[start] = 2 + state[end] = 3 + frames.append(state.copy()) + + for layer in wave_layers[1:]: + for row, col in layer: + if (row, col) not in (start, end): + state[row, col] = 4 + state[start] = 2 + state[end] = 3 + frames.append(state.copy()) + + for row, col in path: + if (row, col) not in (start, end): + state[row, col] = 5 + state[start] = 2 + state[end] = 3 + frames.append(state.copy()) + + return frames 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: # ваш код - return FuncAnimation() + if maze.ndim != 2: + raise ValueError() + + if not _is_inside(maze, *start) or not _is_inside(maze, *end): + raise ValueError() + + if maze[start] == 0 or maze[end] == 0: + raise ValueError() + + _, wave_layers, path = _run_wave_algorithm(maze, start, end) + frames = _build_animation_frames(maze, start, end, wave_layers, path) + + figure, axis = plt.subplots(figsize=(7, 7)) + axis: plt.Axes + + color_map = plt.matplotlib.colors.ListedColormap( + [ + "#1e1e1e", # стена + "#f5f5f5", # проход + "#4caf50", # старт + "#e53935", # финиш + "#42a5f5", # волна + "#ffd54f", # путь + ] + ) + + image = axis.imshow(frames[0], cmap=color_map, vmin=0, vmax=5) + axis.set_title("Волновой алгоритм") + axis.set_xticks([]) + axis.set_yticks([]) + + path_exists = len(path) > 0 + + def update(frame_id: int): + image.set_data(frames[frame_id]) + if frame_id < 2: + axis.set_title("Подготовка") + elif frame_id < len(frames) - (1 if path_exists else 0): + axis.set_title("Распространение волны") + elif path_exists: + axis.set_title("Восстановление кратчайшего пути") + else: + axis.set_title("Путь не существует") + return [image] + + animation = FuncAnimation( + figure, + update, + frames=len(frames), + interval=250, + blit=True, + ) + + if save_path: + animation.save(save_path, writer="pillow", fps=5) + + return animation + 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) - save_path = "labyrinth.gif" # Укажите путь для сохранения анимации + save_path = "solutions/sem02/lesson08/labyrinth.gif" # Укажите путь для сохранения анимации animation = animate_wave_algorithm(maze, start, end, save_path) HTML(animation.to_jshtml()) - + # Пример 2 - - maze_path = "./data/maze.npy" + + maze_path = Path(__file__).parent / "data" / "maze.npy" loaded_maze = np.load(maze_path) # можете поменять, если захотите запустить из других точек start = (2, 0) end = (5, 0) - loaded_save_path = "loaded_labyrinth.gif" + if loaded_maze[start] == 0 or loaded_maze[end] == 0: + passable_cells = np.argwhere(loaded_maze == 1) + if len(passable_cells) < 2: + raise ValueError() + start = tuple(passable_cells[0]) + end = tuple(passable_cells[-1]) + loaded_save_path = "solutions/sem02/lesson08/loaded_labyrinth.gif" loaded_animation = animate_wave_algorithm(loaded_maze, start, end, loaded_save_path) HTML(loaded_animation.to_jshtml()) - - \ No newline at end of file