diff --git a/.github/workflows/code_style.yml b/.github/workflows/lint.yml similarity index 50% rename from .github/workflows/code_style.yml rename to .github/workflows/lint.yml index bd1ae279..3fbf6c03 100644 --- a/.github/workflows/code_style.yml +++ b/.github/workflows/lint.yml @@ -1,6 +1,6 @@ # This is a basic workflow to help you get started with Actions -name: Check code style +name: Check lint # Controls when the workflow will run on: @@ -27,31 +27,15 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - name: Set up Git repository - uses: actions/checkout@v2 - - # Setup Python with version from matrix - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 with: - python-version: ${{ matrix.python-version }} - - # Install requirements - - name: Install requirements - - # Runs command-line programs using the operating system's shell + python-version: '3.x' + - name: Install dependencies run: | - python -m pip install --upgrade pip wheel setuptools - python -m pip install -r requirements.txt - python -m pip list - - # Install pre-commit from .pre-commit-config.yaml - - name: Install pre-commit - run: | - pre-commit install - - # Run pre-commit on all the files in the repo - - name: Run pre-commit + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Pre commit run: | pre-commit run --all-files --color always --verbose --show-diff-on-failure diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml new file mode 100644 index 00000000..2529d97c --- /dev/null +++ b/.github/workflows/mypy.yml @@ -0,0 +1,41 @@ +# This is a basic workflow to help you get started with Actions + +name: Check mypy + +# Controls when the workflow will run +on: + # Triggers the workflow on push or pull request events + [ push, pull_request ] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + + # This workflow contains a single job called "style" + style: + + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # A strategy creates a build matrix for your jobs + strategy: + + # You can define a matrix of different job configurations + matrix: + + # Each option you define in the matrix has a key and value + python-version: [ 3.8 ] + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Run mypy + run: | + mypy project/ tests/ diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml new file mode 100644 index 00000000..3c3f3c47 --- /dev/null +++ b/.github/workflows/pytest.yml @@ -0,0 +1,41 @@ +# This is a basic workflow to help you get started with Actions + +name: Check pytest + +# Controls when the workflow will run +on: + # Triggers the workflow on push or pull request events + [ push, pull_request ] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + + # This workflow contains a single job called "style" + style: + + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # A strategy creates a build matrix for your jobs + strategy: + + # You can define a matrix of different job configurations + matrix: + + # Each option you define in the matrix has a key and value + python-version: [ 3.8 ] + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Run pytest + run: | + python ./scripts/run_tests.py diff --git a/project/__main__.py b/project/__main__.py deleted file mode 100644 index 344b363a..00000000 --- a/project/__main__.py +++ /dev/null @@ -1 +0,0 @@ -print("exec sources directory") diff --git a/project/operation_on_matrix.py b/project/operation_on_matrix.py new file mode 100644 index 00000000..a4d757ec --- /dev/null +++ b/project/operation_on_matrix.py @@ -0,0 +1,89 @@ +# Operations on matrices + + +def matrix_addition(matrix_1: list[list], matrix_2: list[list]): + """ + Adds two matrices together. + + Args: + ----- + matrix_1 (list of lists): The first matrix. + matrix_2 (list of lists): The second matrix. + + Returns: + -------- + list of lists: The sum of the two matrices, or None if the matrices have + different dimensions. + Raises: + TypeError: If either input is not a list of lists. + """ + if len(matrix_1) != len(matrix_2) or len(matrix_1[0]) != len(matrix_2[0]): + return None + else: + matrix_sum = [[0] * len(matrix_1[0]) for _ in range(len(matrix_1))] + for i in range(len(matrix_1)): + for j in range(len(matrix_1[0])): + matrix_sum[i][j] = matrix_1[i][j] + matrix_2[i][j] + return matrix_sum + + +def matrix_multiplication(matrix_1: list[list], matrix_2: list[list]): + """ + The matrix_multiplication function multiplies two matrices represented as nested lists. + + Parameters: + ----------- + matrix_1 (list of lists): The first matrix. + matrix_2 (list of lists): The second matrix. + + Return value: + ------------- + list of lists: The product of two matrices if the number of columns in the first matrix is equal to the number of rows in the second matrix. + None: If the number of columns in the first matrix is not equal to the number of rows in the second matrix. + + Exceptions: + ----------- + TypeError: Occurs if one or both of the input arguments are not lists or nested lists. + + """ + if len(matrix_1[0]) != len(matrix_2): + return None + else: + matrix_mult = [[0] * len(matrix_2[0]) for _ in range(len(matrix_1))] + for i in range(len(matrix_1)): + for j in range(len(matrix_2[0])): + for r in range(len(matrix_2)): + matrix_mult[i][j] += int(matrix_1[i][r]) * int(matrix_2[r][j]) + return matrix_mult + + +def matrix_transposition(matrix: list[list]): + """ + The matrix_transposition function transposes a matrix represented as a nested list. + Transposing a matrix means exchanging rows and columns. + The function passes through each element of the original matrix matrix using nested for loops. + For each matrix[i][j] element (row i, column j), the function assigns the value of the matrix_trans[j][i] element + (row j, column i). Thus, the rows and columns of the original matrix are swapped. + + Parameters: + ----------- + matrix (list of lists): The matrix to be transposed. + + Return value: + ------------- + list of lists: A transposed matrix represented as a nested list. + + Exceptions: + ----------- + TypeError: Occurs if the input argument is not a list or a nested list. + + """ + + if not matrix: + return [] + else: + matrix_trans = [[0] * len(matrix) for _ in range(len(matrix[0]))] + for i in range(len(matrix)): + for j in range(len(matrix[0])): + matrix_trans[j][i] = matrix[i][j] + return matrix_trans diff --git a/project/operation_on_vectors.py b/project/operation_on_vectors.py new file mode 100644 index 00000000..df0d3083 --- /dev/null +++ b/project/operation_on_vectors.py @@ -0,0 +1,69 @@ +# Operation on vectors + +from math import pi, acos + + +def the_scalar_product_of_vectors(vector_1: list[int], vector_2: list[int]): + """ + Function the_scalar_product_of_vectors checks whether the vectors have the same length. If not, it returns None. + Otherwise, the function calculates the scalar pro1duct by multiplying the corresponding elements of the vectors + and summing the results. + + Parameters: + ----------- + vector_1 (list): The first vector, represented as a list of numbers. + vector_2 (list): The second vector, represented as a list of numbers. + + Return value: + ------------- + int: The scalar product of vectors vector_1 and vector_2, if they have the same length. + None: If the vectors have different lengths. + + """ + if len(vector_1) != len(vector_2): + return None + else: + p = 0 + for i in range(len(vector_1)): + p += vector_1[i] * vector_2[i] + return p + + +def vector_length(vector: list[int]): + """ + The vector_length function calculates the length of a vector represented as a list of numbers. + + Parameters: + ----------- + vector (list): A vector represented as a list of numbers. + + Return value: + ------------- + int: The length of the vector, rounded to an integer + + """ + s = 0 + for i in vector: + s += i**2 + return round(pow(s, 0.5), 2) + + +def the_angle_between_the_vectors(vector_1: list[int], vector_2: list[int]): + """ + The_angle_between_the_vectors function calculates the angle between two vectors + given as lists of numbers using the scalar product and the lengths of the vectors. + + Parameters: + ----------- + vector_1 (list): The first vector, represented as a list of numbers. + vector_2 (list): The second vector, represented as a list of numbers. + + Return value: + ------------- + float: The angle between the vectors in radians, with an accuracy of two decimal places. + + """ + s = the_scalar_product_of_vectors(vector_1, vector_2) + angle = s / (vector_length(vector_1) * vector_length(vector_2)) + angle = acos(angle) * 180 / pi + return round(angle) diff --git a/requirements.txt b/requirements.txt index 30544ac2..61500b18 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ black +mypy pre-commit pytest diff --git a/tasks/task1.md b/tasks/task1.md index 2d067506..c8bc0c35 100644 --- a/tasks/task1.md +++ b/tasks/task1.md @@ -1,6 +1,6 @@ # Задача 1. Инициализация рабочего окружения -* **Дедлайн**: 27.09.2024, 23:59 +* **Дедлайн**: 03.10.2024, 23:59 * Полный балл: 5 ## Задача @@ -13,3 +13,4 @@ - [ ] Операции над матрицами: сложение, умножение, транспонирование. - [ ] Добавить тесты покрывающие реализованную функциональность. - [ ] Добавить запуск тестов с помощью `pytest` в `.github/workflows/`. +- [ ] Добавить статическую проверку типов с помощью `mypy` в `.github/workflows/`. diff --git a/tests/test_basic.py b/tests/test_basic.py deleted file mode 100644 index 4811167b..00000000 --- a/tests/test_basic.py +++ /dev/null @@ -1,18 +0,0 @@ -import pytest -import project # on import will print something from __init__ file - - -def setup_module(module): - print("basic setup module") - - -def teardown_module(module): - print("basic teardown module") - - -def test_1(): - assert 1 + 1 == 2 - - -def test_2(): - assert "1" + "1" == "11" diff --git a/tests/test_matrix.py b/tests/test_matrix.py new file mode 100644 index 00000000..68a9fe9f --- /dev/null +++ b/tests/test_matrix.py @@ -0,0 +1,114 @@ +from project.operation_on_matrix import * + +import pytest + + +def test_matrix_addition_valid(): + matrix_1 = [[1, 2], [3, 4]] + matrix_2 = [[5, 6], [7, 8]] + expected_result = [[6, 8], [10, 12]] + assert matrix_addition(matrix_1, matrix_2) == expected_result + + +def test_matrix_addition_different_dimensions(): + matrix_1 = [[1, 2], [3, 4]] + matrix_2 = [[5, 6, 7], [8, 9, 10]] + assert matrix_addition(matrix_1, matrix_2) is None + + +def test_matrix_addition_empty(): + matrix_1 = [[]] + matrix_2 = [[]] + assert matrix_addition(matrix_1, matrix_2) == [[]] + + +def test_matrix_addition_single_element(): + matrix_1 = [[1]] + matrix_2 = [[2]] + expected_result = [[3]] + assert matrix_addition(matrix_1, matrix_2) == expected_result + + +def test_matrix_addition_negative_numbers(): + matrix_1 = [[-1, -2], [-3, -4]] + matrix_2 = [[1, 2], [3, 4]] + expected_result = [[0, 0], [0, 0]] + assert matrix_addition(matrix_1, matrix_2) == expected_result + + +def test_matrix_multiplication_valid(): + matrix_1 = [[1, 2], [3, 4]] + matrix_2 = [[5, 6], [7, 8]] + expected_result = [[19, 22], [43, 50]] + assert matrix_multiplication(matrix_1, matrix_2) == expected_result + + +def test_matrix_multiplication(): + matrix_1 = [[1, 2, 4], [3, 4, 9]] + matrix_2 = [[5, 6], [7, 8], [9, 0]] + expected_result = [[55, 22], [124, 50]] + assert matrix_multiplication(matrix_1, matrix_2) == expected_result + + +def test_matrix_multiplication_different_dimensions(): + matrix_1 = [[1, 2], [3, 4]] + matrix_2 = [[5, 6, 7, 0], [8, 9, 10, 11], [1, 2, 3, 4]] + assert matrix_multiplication(matrix_1, matrix_2) is None + + +def test_matrix_multiplication_empty(): + matrix_1 = [[]] + matrix_2 = [[]] + assert matrix_multiplication(matrix_1, matrix_2) is None + + +def test_matrix_multiplication_single_element(): + matrix_1 = [[1]] + matrix_2 = [[2]] + expected_result = [[2]] + assert matrix_multiplication(matrix_1, matrix_2) == expected_result + + +def test_matrix_multiplication_identity(): + matrix_1 = [[1, 2], [3, 4]] + matrix_2 = [[1, 0], [0, 1]] + assert matrix_multiplication(matrix_1, matrix_2) == matrix_1 + + +def test_matrix_multiplication_negative_numbers(): + matrix_1 = [[-1, -2], [-3, -4]] + matrix_2 = [[1, 2], [3, 4]] + expected_result = [[-7, -10], [-15, -22]] + assert matrix_multiplication(matrix_1, matrix_2) == expected_result + + +def test_matrix_transposition_valid(): + matrix = [[1, 2, 3], [4, 5, 6]] + expected_result = [[1, 4], [2, 5], [3, 6]] + assert matrix_transposition(matrix) == expected_result + + +def test_matrix_transposition_empty(): + matrix = [] + assert matrix_transposition(matrix) == [] + + +def test_matrix_transposition_single_element(): + matrix = [[1]] + assert matrix_transposition(matrix) == [[1]] + + +def test_matrix_transposition_square(): + matrix = [[1, 2], [3, 4]] + expected_result = [[1, 3], [2, 4]] + assert matrix_transposition(matrix) == expected_result + + +def test_matrix_transposition_invalid_input_type(): + with pytest.raises(TypeError): + matrix_transposition(1) # Не список + + +def test_matrix_transposition_invalid_input_type_2(): + with pytest.raises(TypeError): + matrix_transposition([[1, 2], 3]) # Не список списков diff --git a/tests/test_vectors.py b/tests/test_vectors.py new file mode 100644 index 00000000..405ab7e4 --- /dev/null +++ b/tests/test_vectors.py @@ -0,0 +1,54 @@ +from project.operation_on_vectors import * + +import pytest + + +def test_scalar_product_valid(): + vector_1 = [1, 2, 3] + vector_2 = [4, 5, 6] + assert the_scalar_product_of_vectors(vector_1, vector_2) == 32 + + +def test_scalar_product_different_lengths(): + vector_1 = [1, 2, 3] + vector_2 = [4, 5] + assert the_scalar_product_of_vectors(vector_1, vector_2) is None + + +def test_vector_length_valid(): + vector = [3, 4] + assert vector_length(vector) == 5.0 + + +def test_vector_length_empty(): + vector = [] + assert vector_length(vector) == 0.0 + + +def test_vector_length_negative(): + vector = [-3, -4] + assert vector_length(vector) == 5.0 + + +def test_vector_length_zero(): + vector = [0, 0, 0] + assert vector_length(vector) == 0.0 + + +def test_angle_between_vectors_valid(): + vector_1 = [1, 2] + vector_2 = [3, 4] + assert the_angle_between_the_vectors(vector_1, vector_2) == 11 + + +def test_angle_between_vectors_orthogonal(): + vector_1 = [1, 0] + vector_2 = [0, 1] + assert the_angle_between_the_vectors(vector_1, vector_2) == 90 + + +def test_angle_between_vectors_different_lengths(): + vector_1 = [1, 2, 3] + vector_2 = [4, 5] + with pytest.raises(TypeError): + the_angle_between_the_vectors(vector_1, vector_2)