Как применить функцию ко всем элементам списка (произвольной вложенности)

1,00
р.
Отвечая на данный вопрос, я заинтересовался более универсальным решением...
Есть список произвольной вложенности, например:
['1','2', ['1',['2','4',['5','6']]],'7','8']
Необходимо применить функцию ко всем элементам списка (включая все вложенные), сохранив при этом его структуру.
Например преобразовать все элементы в числа и возвести их в квадрат, чтобы получилось:
[1, 4, [1, [4, 16, [25, 36]]], 49, 64]
Я опубликовал свой вариант решения, но мне было бы интересно увидеть альтернативные (более интересные) решения.

Ответ
Чтобы поместу изменить, не создавая новые списки (поиск в глубину—depth-first search (DFS)):
def apply_nested(func, lst, isatom=lambda item: not isinstance(item, list)): for i, item in enumerate(lst): if isatom(item): lst[i] = func(item) else: apply_nested(func, item, isatom)
Здесь isatom() предикат определяет, что является неразрывным элементом (атомом) для заданного алгоритма: apply_nested(func, lst) вызывает func функцию для каждого атома в (глубоковложенном) списке lst. Похожее решение: flatten_gen().
Легко создать нерекурсивный вариант (поиск в ширину—breadth-first search (BFS), если использовать deque.popleft()):
def apply_nested(func, lst, isatom=lambda item: not isinstance(item, list)): stack = [lst] while stack: lst = stack.pop() for i, item in enumerate(lst): if isatom(item): lst[i] = func(item) else: stack.append(item)
Пример:
>>> nested = ['1','2', ['1',['2','4',['5','6']]],'7','8'] >>> apply_nested(lambda atom: int(atom)**2, nested) >>> nested [1, 4, [1, [4, 16, [25, 36]]], 49, 64]
Аналогично, можно определить функции, которые возвращают новые значения, не изменяя ввода (DFS):
def map_nested(func, lst, isatom=lambda item: not isinstance(item, list)): return [func(item) if isatom(item) else map_nested(func, item, isatom) for item in lst]
Нерекурсивный вариант:
def map_nested(func, lst, isatom=lambda item: not isinstance(item, list)): result = [] stack = [(lst, result)] while stack: lst, new_lst = stack.pop() for item in lst: if isatom(item): new_lst.append(func(item)) else: # item is a sublist (collection) sublist = [] new_lst.append(sublist) stack.append((item, sublist)) return result
Пример:
>>> map_nested(lambda atom: int(atom)**2, nested)) [1, 4, [1, [4, 16, [25, 36]]], 49, 64]