Разберите следующий фрагмент Python: def flatten(x): return sum([flatten(y) if isinstance(y, list) else [y] for y in x], []) — как он работает, в чём его недостатки по производительности и памяти и как переписать эффективнее

19 Сен в 04:12
1 +1
0
Ответы
1
Как работает исходный фрагмент
def flatten(x):
return sum([flatten(y) if isinstance(y, list) else [y] for y in x], [])
- Для каждого элемента y в x создаётся либо одноэлементный список [y][y][y] (если y не список), либо результат рекурсивного вызова flatten(y) (если y — список).
- Получив список списков, sum(..., []) последовательно складывает (конкатенирует) эти списки, начиная с пустого списка, давая общий плоский список.
Недостатки по производительности и памяти
- Операция конкатенации списков создаёт новый список и копирует элементы. При последовательном сложении списков получаем квадратичную копию элементов в худшем случае. При n элементов и m «частичных» списков это даёт в худшем случае время порядка O(N2)O(N^2)O(N2). Для примера, если складывать списки длины 1 последовательно, суммарная работа ~1+2+⋯+N=N(N+1)2=O(N2)1+2+\dots+N=\frac{N(N+1)}{2}=O(N^2)1+2++N=2N(N+1) =O(N2).
- Создаются множество промежуточных списков — высокий пиковый расход памяти и лишние копирования.
- Рекурсия может привести к переполнению стека при глубокой вложенности.
- isinstance(y, list) фильтрует только list; кортежи/другие итерируемые объекты, строки и т.п. обрабатываются неунифицированно.
Эффективные варианты
1) Рекурсивный с extend (линейное время и память, но рекурсивная глубина остаётся)
def flatten(x):
res = []
for y in x:
if isinstance(y, list):
res.extend(flatten(y))
else:
res.append(y)
return res
Временная сложность: O(N)O(N)O(N). Память: O(N)O(N)O(N) (без множества промежуточных копий).
2) Генератор (ленивая генерация, меньше пиковой памяти; рекурсия остаётся)
def iter_flatten(x):
for y in x:
if isinstance(y, list):
yield from iter_flatten(y)
else:
yield y
# получить список: list(iter_flatten(x))
3) Итеративный стекный вариант (избегает глубокой рекурсии)
def flatten_iter(x):
res = []
stack = list(reversed(x))
while stack:
item = stack.pop()
if isinstance(item, list):
stack.extend(reversed(item))
else:
res.append(item)
return res
Этот вариант даёт порядок обхода тот же, что и рекурсивный, с временной сложностью O(N)O(N)O(N) и без рекурсивного стека Python.
4) Универсальность типов
Если нужно обрабатывать любые итерируемые, а не строки и байты, проверку можно делать через collections.abc.Iterable и исключать (str, bytes):
from collections.abc import Iterable
def iter_flatten_any(x):
for y in x:
if isinstance(y, Iterable) and not isinstance(y, (str, bytes)):
yield from iter_flatten_any(y)
else:
yield y
Короткое резюме
- Исходный код прост, но неэффективен: время и копирования до O(N2)O(N^2)O(N2), много промежуточной памяти и риск переполнения стека.
- Лучше использовать extend/генератор/итеративный стек — они дают линейное время O(N)O(N)O(N), линейную память O(N)O(N)O(N) (без лишних промежуточных копий) и при итеративном варианте — отсутствие рекурсивной глубины.
19 Сен в 04:16
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир