Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions .github/workflows/python-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Python Tests

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.9, 3.11, 3.12]

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest
pip install -e .

- name: Run tests
run: |
pytest

ruff:
name: Ruff Check
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'

- name: Install Ruff
run: pip install ruff

- name: Run linting
run: ruff check .

82 changes: 82 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# ruff.toml
target-version = "py311"
line-length = 100

# включить все основные правила
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # Pyflakes
"I", # isort
"N", # pep8-naming
"UP", # pyupgrade
"YTT", # flake8-2020
"S", # flake8-bandit
"A", # flake8-builtins
"COM", # flake8-commas
"C4", # flake8-comprehensions
"DTZ", # flake8-datetimez
"T10", # flake8-debugger
"EM", # flake8-errmsg
"EXE", # flake8-executable
"ISC", # flake8-implicit-str-concat
"ICN", # flake8-import-conventions
"G", # flake8-logging-format
"INP", # flake8-no-pep420
"PIE", # flake8-pie
"T20", # flake8-print
"PYI", # flake8-pyi
"PT", # flake8-pytest-style
"Q", # flake8-quotes
"RSE", # flake8-raise
"RET", # flake8-return
"SLF", # flake8-self
"SIM", # flake8-simplify
"TID", # flake8-tidy-imports
"TCH", # flake8-type-checking
"INT", # flake8-gettext
"ARG", # flake8-unused-arguments
"FBT", # flake8-boolean-trap
"B", # flake8-bugbear
"AIR", # flake8-airflow
"PERF", # flake8-perflint
]

# игнорировать правила
ignore = [
"E501", # line too long - handled by formatter
"S101", # assert used - ok in tests
"T201", # print found - sometimes needed
"COM812", # trailing comma missing - not always required
]

# настройки для конкретных файлов
[per-file-ignores]
"__init__.py" = ["F401"] # Unused imports allowed in __init__.py
"tests/**" = ["S101", "SLF001"] # Allow assert and self in tests
"**/migrations/**" = ["ALL"] # Ignore all in migrations

# настройки форматтера
[format]
indent-style = "space"
quote-style = "double"
skip-magic-trailing-comma = false
line-ending = "auto"

# настройки для конкретных правил
[flake8-quotes]
docstring-quotes = "double"
inline-quotes = "double"

[flake8-tidy-imports]
ban-relative-imports = "all"

[isort]
known-first-party = ["myapp"]
lines-after-imports = 2
combine-as-imports = true
split-on-trailing-comma = true

[tool.pytest.ini_options]
pythonpath = ["src"]
testpaths = ["tests"]
10 changes: 10 additions & 0 deletions src/bubble_sort.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
def bubble_sort(arr):
if not arr:
return arr.copy()
result = arr.copy()
n = len(result)
for i in range(n):
for j in range(0, n - i - 1):
if result[j] > result[j + 1]:
result[j], result[j + 1] = result[j + 1], result[j]
return result
36 changes: 36 additions & 0 deletions src/heap_sort.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# преобразование в двоичную кучу с корнем i (индекс в arr); n - размер кучи
def heapify(arr, n, i):
largest = i
left = 2 * i + 1
right = 2 * i + 2

# проверяем существует ли левый дочерний элемент больший, чем корень
if left < n and arr[i] < arr[left]:
largest = left

# проверяем существует ли правый дочерний элемент больший, чем корень
if right < n and arr[largest] < arr[right]:
largest = right

# заменяем корень, если нашёлся элемент больше
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]

# применяем heapify к корню
heapify(arr, n, largest)


# сортировка массива (основная функция)
def heap_sort(arr):
n = len(arr)

# строим max-heap
for i in range(n, -1, -1):
heapify(arr, n, i)

# переворачиваем массив
for i in range(n - 1, 0, -1):
arr[i], arr[0] = arr[0], arr[i] # свап
heapify(arr, i, 0)

return arr
55 changes: 55 additions & 0 deletions src/merge_sort.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
def merge(left, right):
"""
Сливает два отсортированных массива в один отсортированный

Args:
left: левый отсортированный массив
right: правый отсортированный массив

Returns:
объединенный отсортированный массив
"""
result = []
i = j = 0 # указатели для left и right

# Сравниваем элементы и добавляем меньший в результат
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1

# Добавляем оставшиеся элементы (если есть)
result.extend(left[i:])
result.extend(right[j:])

return result


def merge_sort(arr):
"""
Основная функция сортировки слиянием

Args:
arr: массив для сортировки

Returns:
отсортированный массив
"""
# Базовый случай: массивы длиной 0 или 1 уже отсортированы
if len(arr) <= 1:
return arr

# Разделяем массив на две половины
mid = len(arr) // 2
left_half = arr[:mid]
right_half = arr[mid:]

# Рекурсивно сортируем каждую половину
left_sorted = merge_sort(left_half)
right_sorted = merge_sort(right_half)

# Сливаем отсортированные половины
return merge(left_sorted, right_sorted)
12 changes: 12 additions & 0 deletions src/quick_sort.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
def quick_sort(arr):
if len(arr) <= 1:
return arr # Базовый случай: массив из 0 или 1 элемента уже отсортирован

pivot = arr[len(arr) // 2] # Выбираем середину в качестве опорного элемента

left = [x for x in arr if x < pivot] # Массив элементов меньше опорного
middle = [x for x in arr if x == pivot] # Массив элементов, равных опорному
right = [x for x in arr if x > pivot] # Массив элементов больше опорного

# Рекурсивно сортируем левую и правую части, объединяем все вместе
return quick_sort(left) + middle + quick_sort(right)
66 changes: 66 additions & 0 deletions tests/test_bubble_sort.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from bubble_sort import bubble_sort
from random import randint, uniform
import pytest

"""Обычные тесты"""


@pytest.mark.parametrize(
["n"], [(([randint(0, 300) for _ in range(10)]),) for _ in range(10)]
)
def test_heap_sort_positive(n):
"""Тест с неотрицательными числами"""
assert bubble_sort(n) == sorted(n)


@pytest.mark.parametrize(
["n"], [(([randint(-300, -1) for _ in range(10)]),) for _ in range(10)]
)
def test_heap_sort_negative(n):
"""Тест с отрицательными числами"""
assert bubble_sort(n) == sorted(n)


@pytest.mark.parametrize(
["n"], [(([uniform(-50, 50) for _ in range(10)]),) for _ in range(10)]
)
def test_float_numbers(n):
"""Тест с числами с плавающей точкой"""
assert bubble_sort(n) == sorted(n)


"""Тесты крайних случаев"""


def test_empty_list():
"""Тест пустого списка"""
assert bubble_sort([]) == []


def test_single_element():
"""Тест одного элемента"""
assert bubble_sort([5]) == [5]


def test_large_random_list():
"""Тест большого случайного списка"""
test_list = [randint(-1000, 1000) for _ in range(1000)]
assert bubble_sort(test_list) == sorted(test_list)


def test_very_large_numbers():
"""Тест с очень большими числами"""
assert bubble_sort([2 ** 100, 0, -1, -2 ** 11]) == [-2 ** 11, -1, 0, 2 ** 100]


def test_already_sorted():
"""Тест уже отсортированного массива"""
assert bubble_sort([1, 2, 3, 4, 5]) == [1, 2, 3, 4, 5]

def test_reverse_sorted():
"""Тест обратно сортированного массива"""
assert bubble_sort([5, 4, 3, 2, 1]) == [1, 2, 3, 4, 5]

def test_duplicates():
"""Тест с дубликатами"""
assert bubble_sort([3, 1, 4, 1, 5, 9, 2, 6, 5]) == [1, 1, 2, 3, 4, 5, 5, 6, 9]
Loading