From c35f62d640c772504bf99b3ddd3a5b5373c89bb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BE=D1=84=D0=B8=D1=8F=20=D0=A8=D0=B2=D0=BE=D1=80?= =?UTF-8?q?=D0=BE=D0=B1?= Date: Fri, 31 Jan 2025 13:53:07 +0300 Subject: [PATCH 1/8] feat(matrix): Implement class Matrix --- project/matrix.py | 26 ++++++++++++++++++++++++++ project/vectors.py | 0 tests/test_basic.py | 18 ------------------ 3 files changed, 26 insertions(+), 18 deletions(-) create mode 100644 project/matrix.py create mode 100644 project/vectors.py delete mode 100644 tests/test_basic.py diff --git a/project/matrix.py b/project/matrix.py new file mode 100644 index 00000000..8c4503cb --- /dev/null +++ b/project/matrix.py @@ -0,0 +1,26 @@ +class Matrix: + def __init__(self, data): + self.data = data + self.rows = len(data) + self.cols = len(data[0]) if data else 0 + + def __str__(self): + return "\n".join(["\t".join(map(str, row)) for row in self.data]) + + def add(self, other): + if self.rows != other.rows or self.cols != other.cols: + raise ValueError("Matrices must have the same dimensions for addition.") + + result = [[self.data[i][j] + other.data[i][j] for j in range(self.cols)] for i in range(self.rows)] + return Matrix(result) + + def multiply(self, other): + if self.cols != other.rows: + raise ValueError("Number of columns in the first matrix must equal the number of rows in the second matrix.") + + result = [[sum(self.data[i][k] * other.data[k][j] for k in range(self.cols)) for j in range(other.cols)] for i in range(self.rows)] + return Matrix(result) + + def transpose(self): + result = [[self.data[j][i] for j in range(self.rows)] for i in range(self.cols)] + return Matrix(result) diff --git a/project/vectors.py b/project/vectors.py new file mode 100644 index 00000000..e69de29b 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" From e506ff1191af84d6b94dfa48bcdf3906b80adca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BE=D1=84=D0=B8=D1=8F=20=D0=A8=D0=B2=D0=BE=D1=80?= =?UTF-8?q?=D0=BE=D0=B1?= Date: Fri, 31 Jan 2025 13:58:46 +0300 Subject: [PATCH 2/8] feat(vectors): Implement class vector --- project/vectors.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/project/vectors.py b/project/vectors.py index e69de29b..02c9b9a9 100644 --- a/project/vectors.py +++ b/project/vectors.py @@ -0,0 +1,32 @@ +import math +from typing import List + + +class Vector: + def __init__(self, components: List[float]): + self.components = components + + def __str__(self): + return f"Vector({self.components})" + + def length(self) -> float: + return math.sqrt(sum(x ** 2 for x in self.components)) + + def dot_product(self, other: 'Vector') -> float: + if len(self.components) != len(other.components): + raise ValueError("Vectors must have the same dimension for dot product") + return sum(x * y for x, y in zip(self.components, other.components)) + + def angle_between(self, other: 'Vector') -> float: + dot_prod = self.dot_product(other) + length_self = self.length() + length_other = other.length() + + if length_self == 0 or length_other == 0: + raise ValueError("Cannot compute angle with zero-length vector") + + cos_theta = dot_prod / (length_self * length_other) + # Ограничиваем значение cos_theta из-за возможных ошибок округления + cos_theta = max(-1.0, min(1.0, cos_theta)) + return math.acos(cos_theta) + From 1b5669bf09e704e5012bd6a7beb9e3ea51ca827a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BE=D1=84=D0=B8=D1=8F=20=D0=A8=D0=B2=D0=BE=D1=80?= =?UTF-8?q?=D0=BE=D0=B1?= Date: Fri, 31 Jan 2025 14:15:28 +0300 Subject: [PATCH 3/8] feat(test_vectors): Imlement unit tests for module vectors.py --- tests/test_vectors.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/test_vectors.py diff --git a/tests/test_vectors.py b/tests/test_vectors.py new file mode 100644 index 00000000..e82b5932 --- /dev/null +++ b/tests/test_vectors.py @@ -0,0 +1,28 @@ +import pytest +import math +from project.vectors import Vector +def test_vector_length(): + v = Vector([3, 4]) + assert v.length() == 5 + +def test_dot_product(): + v1 = Vector([1, 2, 3]) + v2 = Vector([4, -5, 6]) + assert v1.dot_product(v2) == 12 + +def test_angle_between(): + v1 = Vector([1, 0]) + v2 = Vector([0, 1]) + assert math.isclose(v1.angle_between(v2), math.pi / 2, rel_tol=1e-6) + +def test_dot_product_dimension_mismatch(): + v1 = Vector([1, 2, 3]) + v2 = Vector([4, 5]) + with pytest.raises(ValueError, match="Vectors must have the same dimension for dot product"): + v1.dot_product(v2) + +def test_angle_with_zero_length_vector(): + v1 = Vector([0, 0, 0]) + v2 = Vector([1, 2, 3]) + with pytest.raises(ValueError, match="Cannot compute angle with zero-length vector"): + v1.angle_between(v2) From a961b232f44c0db5ba2c160e6d3da70d36eb835a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BE=D1=84=D0=B8=D1=8F=20=D0=A8=D0=B2=D0=BE=D1=80?= =?UTF-8?q?=D0=BE=D0=B1?= Date: Fri, 31 Jan 2025 15:50:16 +0300 Subject: [PATCH 4/8] feat(test_matrix): Implement unit tests for module matrix.py --- tests/test_matrix.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 tests/test_matrix.py diff --git a/tests/test_matrix.py b/tests/test_matrix.py new file mode 100644 index 00000000..0726fe6b --- /dev/null +++ b/tests/test_matrix.py @@ -0,0 +1,31 @@ +import pytest +from project.matrix import Matrix + +def test_matrix_addition(): + A = Matrix([[1, 2], [3, 4]]) + B = Matrix([[5, 6], [7, 8]]) + expected = Matrix([[6, 8], [10, 12]]) + assert A.add(B).data == expected.data + +def test_matrix_addition_dimension_mismatch(): + A = Matrix([[1, 2, 3], [4, 5, 6]]) + B = Matrix([[7, 8], [9, 10]]) + with pytest.raises(ValueError, match="Matrices must have the same dimensions for addition."): + A.add(B) + +def test_matrix_multiplication(): + A = Matrix([[1, 2], [3, 4]]) + B = Matrix([[2, 0], [1, 2]]) + expected = Matrix([[4, 4], [10, 8]]) + assert A.multiply(B).data == expected.data + +def test_matrix_multiplication_dimension_mismatch(): + A = Matrix([[1, 2], [3, 4]]) + B = Matrix([[5, 6, 7]]) + with pytest.raises(ValueError, match="Number of columns in the first matrix must equal the number of rows in the second matrix."): + A.multiply(B) + +def test_matrix_transpose(): + A = Matrix([[1, 2, 3], [4, 5, 6]]) + expected = Matrix([[1, 4], [2, 5], [3, 6]]) + assert A.transpose().data == expected.data From 5d40a95598f1f06c7c2b1b350bf634e451f63dfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BE=D1=84=D0=B8=D1=8F=20=D0=A8=D0=B2=D0=BE=D1=80?= =?UTF-8?q?=D0=BE=D0=B1?= Date: Fri, 31 Jan 2025 16:23:44 +0300 Subject: [PATCH 5/8] feat(docstring & type hints): Implement docstring and type hints for modules matrix.py and vectors.py --- project/matrix.py | 58 ++++++++++++++++++++++++++++++++++++++-------- project/vectors.py | 42 +++++++++++++++++++++++++++++---- 2 files changed, 85 insertions(+), 15 deletions(-) diff --git a/project/matrix.py b/project/matrix.py index 8c4503cb..dd7ea869 100644 --- a/project/matrix.py +++ b/project/matrix.py @@ -1,26 +1,64 @@ +from typing import List + + class Matrix: - def __init__(self, data): - self.data = data - self.rows = len(data) - self.cols = len(data[0]) if data else 0 + """ + Класс для работы с матрицами, включая сложение, умножение и транспонирование. + """ + + def __init__(self, data: List[List[float]]): + """ + Инициализирует матрицу. + + :param data: Двумерный список чисел, представляющий матрицу. + """ + self.data: List[List[float]] = data + self.rows: int = len(data) + self.cols: int = len(data[0]) if data else 0 - def __str__(self): + def __str__(self) -> str: + """ + Возвращает строковое представление матрицы. + + :return: Строка, представляющая матрицу. + """ return "\n".join(["\t".join(map(str, row)) for row in self.data]) - def add(self, other): + def add(self, other: "Matrix") -> "Matrix": + """ + Складывает две матрицы. + + :param other: Вторая матрица для сложения. + :return: Новая матрица, представляющая сумму. + :raises ValueError: Если матрицы имеют разные размеры. + """ if self.rows != other.rows or self.cols != other.cols: raise ValueError("Matrices must have the same dimensions for addition.") result = [[self.data[i][j] + other.data[i][j] for j in range(self.cols)] for i in range(self.rows)] return Matrix(result) - def multiply(self, other): + def multiply(self, other: "Matrix") -> "Matrix": + """ + Умножает две матрицы. + + :param other: Вторая матрица для умножения. + :return: Новая матрица, представляющая произведение. + :raises ValueError: Если число столбцов первой матрицы не равно числу строк второй матрицы. + """ if self.cols != other.rows: - raise ValueError("Number of columns in the first matrix must equal the number of rows in the second matrix.") + raise ValueError( + "Number of columns in the first matrix must equal the number of rows in the second matrix.") - result = [[sum(self.data[i][k] * other.data[k][j] for k in range(self.cols)) for j in range(other.cols)] for i in range(self.rows)] + result = [[sum(self.data[i][k] * other.data[k][j] for k in range(self.cols)) for j in range(other.cols)] for i + in range(self.rows)] return Matrix(result) - def transpose(self): + def transpose(self) -> "Matrix": + """ + Транспонирует матрицу, меняя строки и столбцы местами. + + :return: Новая транспонированная матрица. + """ result = [[self.data[j][i] for j in range(self.rows)] for i in range(self.cols)] return Matrix(result) diff --git a/project/vectors.py b/project/vectors.py index 02c9b9a9..3b690849 100644 --- a/project/vectors.py +++ b/project/vectors.py @@ -3,21 +3,54 @@ class Vector: + """ + Класс для работы с векторами в многомерном пространстве. + """ + def __init__(self, components: List[float]): - self.components = components + """ + Инициализирует вектор с заданными компонентами. + + :param components: Список чисел, представляющих компоненты вектора. + """ + self.components: List[float] = components - def __str__(self): + def __str__(self) -> str: + """ + Возвращает строковое представление вектора. + + :return: Строка, представляющая вектор. + """ return f"Vector({self.components})" def length(self) -> float: + """ + Вычисляет длину (модуль) вектора. + + :return: Длина вектора. + """ return math.sqrt(sum(x ** 2 for x in self.components)) - def dot_product(self, other: 'Vector') -> float: + def dot_product(self, other: "Vector") -> float: + """ + Вычисляет скалярное произведение с другим вектором. + + :param other: Второй вектор. + :return: Скалярное произведение векторов. + :raises ValueError: Если векторы имеют разные размеры. + """ if len(self.components) != len(other.components): raise ValueError("Vectors must have the same dimension for dot product") return sum(x * y for x, y in zip(self.components, other.components)) - def angle_between(self, other: 'Vector') -> float: + def angle_between(self, other: "Vector") -> float: + """ + Вычисляет угол между двумя векторами в радианах. + + :param other: Второй вектор. + :return: Угол в радианах. + :raises ValueError: Если хотя бы один из векторов имеет нулевую длину. + """ dot_prod = self.dot_product(other) length_self = self.length() length_other = other.length() @@ -29,4 +62,3 @@ def angle_between(self, other: 'Vector') -> float: # Ограничиваем значение cos_theta из-за возможных ошибок округления cos_theta = max(-1.0, min(1.0, cos_theta)) return math.acos(cos_theta) - From f15e6c734b9df7fb6fa7947fcd39b4b7061e3a8e Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 5 Apr 2025 18:09:38 +0300 Subject: [PATCH 6/8] feat(ci): ci added --- .github/workflows/ci.yaml | 31 +++++++++++++++++++++++++++++++ mypy.ini | 2 ++ 2 files changed, 33 insertions(+) create mode 100644 .github/workflows/ci.yaml create mode 100644 mypy.ini diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..75a2e367 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,31 @@ +name: Run Tests + +on: + [ push, pull_request ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.8' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Install mypy + run: | + python -m pip install mypy + - name: Run mypy + run: | + mypy . + - name: Run tests + run: | + python ./scripts/run_tests.py diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..624cd195 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,2 @@ +[mypy] +exclude = venv/ From c944d2480a26d6da66f3d612261bbfbe35b2afd3 Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 5 Apr 2025 18:15:52 +0300 Subject: [PATCH 7/8] chore(code): code beautify --- project/matrix.py | 17 +++++++++++++---- project/vectors.py | 2 +- tests/test_matrix.py | 14 ++++++++++++-- tests/test_vectors.py | 14 ++++++++++++-- 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/project/matrix.py b/project/matrix.py index dd7ea869..a724d165 100644 --- a/project/matrix.py +++ b/project/matrix.py @@ -35,7 +35,10 @@ def add(self, other: "Matrix") -> "Matrix": if self.rows != other.rows or self.cols != other.cols: raise ValueError("Matrices must have the same dimensions for addition.") - result = [[self.data[i][j] + other.data[i][j] for j in range(self.cols)] for i in range(self.rows)] + result = [ + [self.data[i][j] + other.data[i][j] + for j in range(self.cols)] for i in range(self.rows) + ] return Matrix(result) def multiply(self, other: "Matrix") -> "Matrix": @@ -48,10 +51,16 @@ def multiply(self, other: "Matrix") -> "Matrix": """ if self.cols != other.rows: raise ValueError( - "Number of columns in the first matrix must equal the number of rows in the second matrix.") + "Number of columns in the first matrix must equal the number of rows in the second matrix." + ) - result = [[sum(self.data[i][k] * other.data[k][j] for k in range(self.cols)) for j in range(other.cols)] for i - in range(self.rows)] + result = [ + [ + sum(self.data[i][k] * other.data[k][j] for k in range(self.cols)) + for j in range(other.cols) + ] + for i in range(self.rows) + ] return Matrix(result) def transpose(self) -> "Matrix": diff --git a/project/vectors.py b/project/vectors.py index 3b690849..011ae591 100644 --- a/project/vectors.py +++ b/project/vectors.py @@ -29,7 +29,7 @@ def length(self) -> float: :return: Длина вектора. """ - return math.sqrt(sum(x ** 2 for x in self.components)) + return math.sqrt(sum(x**2 for x in self.components)) def dot_product(self, other: "Vector") -> float: """ diff --git a/tests/test_matrix.py b/tests/test_matrix.py index 0726fe6b..9dcd162c 100644 --- a/tests/test_matrix.py +++ b/tests/test_matrix.py @@ -1,30 +1,40 @@ import pytest from project.matrix import Matrix + def test_matrix_addition(): A = Matrix([[1, 2], [3, 4]]) B = Matrix([[5, 6], [7, 8]]) expected = Matrix([[6, 8], [10, 12]]) assert A.add(B).data == expected.data + def test_matrix_addition_dimension_mismatch(): A = Matrix([[1, 2, 3], [4, 5, 6]]) B = Matrix([[7, 8], [9, 10]]) - with pytest.raises(ValueError, match="Matrices must have the same dimensions for addition."): + with pytest.raises( + ValueError, match="Matrices must have the same dimensions for addition." + ): A.add(B) + def test_matrix_multiplication(): A = Matrix([[1, 2], [3, 4]]) B = Matrix([[2, 0], [1, 2]]) expected = Matrix([[4, 4], [10, 8]]) assert A.multiply(B).data == expected.data + def test_matrix_multiplication_dimension_mismatch(): A = Matrix([[1, 2], [3, 4]]) B = Matrix([[5, 6, 7]]) - with pytest.raises(ValueError, match="Number of columns in the first matrix must equal the number of rows in the second matrix."): + with pytest.raises( + ValueError, + match="Number of columns in the first matrix must equal the number of rows in the second matrix." + ): A.multiply(B) + def test_matrix_transpose(): A = Matrix([[1, 2, 3], [4, 5, 6]]) expected = Matrix([[1, 4], [2, 5], [3, 6]]) diff --git a/tests/test_vectors.py b/tests/test_vectors.py index e82b5932..12b3e2e2 100644 --- a/tests/test_vectors.py +++ b/tests/test_vectors.py @@ -1,28 +1,38 @@ import pytest import math from project.vectors import Vector + + def test_vector_length(): v = Vector([3, 4]) assert v.length() == 5 + def test_dot_product(): v1 = Vector([1, 2, 3]) v2 = Vector([4, -5, 6]) assert v1.dot_product(v2) == 12 + def test_angle_between(): v1 = Vector([1, 0]) v2 = Vector([0, 1]) assert math.isclose(v1.angle_between(v2), math.pi / 2, rel_tol=1e-6) + def test_dot_product_dimension_mismatch(): v1 = Vector([1, 2, 3]) v2 = Vector([4, 5]) - with pytest.raises(ValueError, match="Vectors must have the same dimension for dot product"): + with pytest.raises( + ValueError, match="Vectors must have the same dimension for dot product" + ): v1.dot_product(v2) + def test_angle_with_zero_length_vector(): v1 = Vector([0, 0, 0]) v2 = Vector([1, 2, 3]) - with pytest.raises(ValueError, match="Cannot compute angle with zero-length vector"): + with pytest.raises( + ValueError, match="Cannot compute angle with zero-length vector" + ): v1.angle_between(v2) From 68bce47f15fa1334fccb24481cdc52f62ff18c9c Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 5 Apr 2025 18:18:51 +0300 Subject: [PATCH 8/8] chore(code): code beautify --- project/matrix.py | 4 ++-- tests/test_matrix.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/project/matrix.py b/project/matrix.py index a724d165..9c7ac409 100644 --- a/project/matrix.py +++ b/project/matrix.py @@ -36,8 +36,8 @@ def add(self, other: "Matrix") -> "Matrix": raise ValueError("Matrices must have the same dimensions for addition.") result = [ - [self.data[i][j] + other.data[i][j] - for j in range(self.cols)] for i in range(self.rows) + [self.data[i][j] + other.data[i][j] for j in range(self.cols)] + for i in range(self.rows) ] return Matrix(result) diff --git a/tests/test_matrix.py b/tests/test_matrix.py index 9dcd162c..dcf5bfda 100644 --- a/tests/test_matrix.py +++ b/tests/test_matrix.py @@ -30,7 +30,7 @@ def test_matrix_multiplication_dimension_mismatch(): B = Matrix([[5, 6, 7]]) with pytest.raises( ValueError, - match="Number of columns in the first matrix must equal the number of rows in the second matrix." + match="Number of columns in the first matrix must equal the number of rows in the second matrix.", ): A.multiply(B)