Первым делом нужно внимательно прочитать условие задачи. Самое главное в ней то, что дана матрица вида
+-----+-----+-----+-----+
| 10 | 20 | 30 | 40 |
+-----+-----+-----+-----+
| 50 | 60 | 70 | 80 |
+-----+-----+-----+-----+
| 90 | 100 | 110 | 120 |
+-----+-----+-----+-----+
| 130 | 140 | 150 | 160 |
+-----+-----+-----+-----+и нужно получить список, содержащий результат обхода полученной матрицы по спирали [10, 50, 90, 130, 140, 150, 160, 120, 80, 40, 30, 20, 60, 100, 110, 70].
Для удобства этот список можно также представить в виде матрицы
[
10, 50, 90, 130,
140, 150, 160, 120,
80, 40, 30, 20,
60, 100, 110, 70,
]Всё остальное в задании просто детали.
По условию задачи матрицу мы можем получить по сети. В требованиях указано, что нужно использовать одну из асинхронных библиотек: aiohttp, httpx или другого компонента на asyncio.
Напишем функцию get_matrix, которая пока просто делает http-запрос и возвращает текст ответа. Сразу же проверим с помощью метода raise_for_status(), что во время запроса не было никаких ошибок.
import httpx
import asyncio
from typing import List
async def get_matrix(url: str) -> List[int]:
response = httpx.get(url)
response.raise_for_status()
return response.text
if __name__ == '__main__':
_url = "https://raw.githubusercontent.com/avito-tech/python-trainee-assignment/main/matrix.txt"
_result = asyncio.run(get_matrix(_url))
print(_result)Преобразуем текст ответа к квадратной матрице, содержащий числа, в функции.
import re
from typing import List
def get_matrix_from_text(text: str) -> List[List[int]]:
lines = text.split('\n')
matrix = []
for line in lines:
if numbers := re.findall(r'\d+', line):
matrix.append([int(x) for x in numbers])
return matrixМожно разбить текст по символу \n. Так мы получим список из строк двух видов:
+-----+-----+-----+-----+их можно пропустить;| 10 | 20 | 30 | 40 |здесь нужно достать числа. Для этого есть множества вариантов. В моём варианте мы достаём их через регулярные выражение.
Это самый сложный этап при решении задачи. Нужно реализовать функцию, которая принимает числовую матрицу и возвращает список чисел.
Напишем внешний цикл while top <= bottom and left <= right:, который проверяет границы матрицы.
В нём же будет 4 цикла for для сбора чисел в список против часовой стрелки, начиная с левого верхнего угла.
from typing import List
def spiral(matrix: List[List[int]]) -> List[int]:
if not matrix:
return []
result = []
# задаём размерность матрицы и её границы
rows = len(matrix)
cols = len(matrix[0])
top = 0
bottom = rows - 1
left = 0
right = cols - 1
while top <= bottom and left <= right:
# обход чисел сверху вниз в левом столбце
for col in range(left, bottom + 1):
result.append(matrix[col][top])
left += 1
# справа налево нижний ряд
for row in range(left, bottom + 1):
result.append(matrix[right][row])
bottom -= 1
# снизу в вверх правый столбец
if top <= bottom:
for col in range(bottom, top - 1, -1):
result.append(matrix[col][right])
right -= 1
# справа на налево верхний ряд.
if left <= right:
for row in range(right, left - 1, -1):
result.append(matrix[top][row])
top += 1
return resultТак как этот алгоритм самая сложная часть в задании, то нужно написать на него тесты Ограничимся 4 тестовыми сценариями
import pytest
from main import spiral
@pytest.mark.parametrize("matrix, expected_data", [
([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
],
[
1, 5, 9, 13,
14, 15, 16, 12,
8, 4, 3, 2,
6, 10, 11, 7,
]
),
([
[0, 100000000],
[0, 0]
],
[0, 0, 0, 100000000]),
([], []),
([[1]], [1]),
]
)
def test_spiral(matrix, expected_data):
assert spiral(matrix) == expected_dataФинальное представление функции get_matrix будет иметь вид
import re
import asyncio
import httpx
from typing import List
async def get_matrix(url: str) -> List[int]:
response = httpx.get(url)
response.raise_for_status()
matrix = get_matrix_from_text(response.text)
return spiral(matrix)
if __name__ == '__main__':
_url = "https://raw.githubusercontent.com/avito-tech/python-trainee-assignment/main/matrix.txt"
_result = asyncio.run(get_matrix(_url))
TRAVERSAL = [
10, 50, 90, 130,
140, 150, 160, 120,
80, 40, 30, 20,
60, 100, 110, 70,
]
assert _result == TRAVERSALДля решения этой задачи мы использовали несколько внешних зависимостей. Правильно их перечислить в файле requirements.txt
httpx==0.27.0
asyncio==3.4.3
pytest==8.2.2