def map_nested(lst, func=lambda x: x):
assert isinstance(lst, list), '"lst" parameter is NOT a list'
return [map_nested(lst[i], func)
if isinstance(lst[i], list)
else func(lst[i])
for i in range(len(lst))]
import timeit
def map_nested(lst, func=lambda x: x, forbidden_types=(str, int)):
container_type = type(lst)
if hasattr(lst, '__iter__') and type(lst) not in forbidden_types:
return container_type(map_nested(item, func) for item in lst)
else:
try:
return func(lst)
except:
return lst
def map_nested_1line(lst, func=lambda x: x):
return [map_nested_1line(item, func) for item in lst] if type(lst) is list else func(lst)
# Also non-recursive. Yay!
def map_nested_inplace(lst, func=lambda x: x, forbidden_types=(str, int)):
# forbidden_types не используется
processed_elements = 0
# assert type(lst) is list # blah-blah
current_container = lst
containers_repo = [[current_container, 0]]
# До тех пор, пока не были обработаны все списки и под-списки
while len(containers_repo) != 0:
while True:
try:
# Следующий элемент в текущем списке.
# Может быть как числом, так и новым под-списком
next_one = current_container[containers_repo[-1][1]]
break
# Исключение означает, что под-список кончился.
# Т.к. он кончился, то убираем его из хранилища
# и пробуем извлечь следующий элемент
except IndexError:
containers_repo.pop()
if len(containers_repo) == 0:
break
current_container = containers_repo[-1][0]
if len(containers_repo) != 0:
if type(next_one) is list:
# Это под-список, а не число.
# Заносим под-список в хранилище и
# на следующей итерации открываем уже его
containers_repo[-1][1] += 1
current_container = next_one
containers_repo.append([current_container, 0])
else:
current_container[containers_repo[-1][1]] = func(current_container[containers_repo[-1][1]])
containers_repo[-1][1] += 1
# ради небольшой проверки
processed_elements += 1
# set также работает, но непохоже, чтобы он сохранял порядок
lst = ['1', 'an error', ('1', ['2', '4', (5, '6')]), '7', 8]
lst_simple = ['1', '2', ['1', ['2', '4', ['5', '6']]], '7', '8']
lst_another_simple = ['1', '2', ['1', ['2', '4', ['5', '6']], ['6', '6', '6'], ['8', '8']], '7', ['11'], '8']
print(map_nested(lst, lambda x: int(x)**2))
print(map_nested_1line(lst_simple, lambda x: int(x)**2))
print(map_nested_1line([], lambda x: int(x)**2))
map_nested_inplace(lst_another_simple, lambda x: int(x)**2)
print(lst_another_simple)
还有一些测试:
import random
test_array = []
container_tree = [test_array]
current_container = container_tree[-1]
TOTAL_AMOUNT = 10000
NEW_LEVEL_PROBABILITY = 0.5
for i in range(TOTAL_AMOUNT):
if random.random() >= NEW_LEVEL_PROBABILITY:
current_container.append([])
container_tree.append(current_container[-1])
current_container = current_container[-1]
elif len(container_tree) > 1:
current_container = container_tree[-2]
current_container.append(str(random.randint(0, 20)))
# Для работоспособности рекурсивных методов
# Впрочем, без старта новго потока с threading.stack_size(<Big value>) все равно не будет работать :(
import sys
if sys.getrecursionlimit() < len(container_tree) * 2:
sys.setrecursionlimit(len(container_tree) * 2)
setup_statement = """from __main__ import test_array, """
# Не используется lambda x**2, потому что
# в inplace квадраты будут накатываться до тех пор,
# пока хватит памяти - список для каждого прохода должен генерироваться заново
print(timeit.timeit("map_nested_inplace(test_array)", setup=setup_statement + "map_nested_inplace", number=100))
print(timeit.timeit("map_nested_1line(test_array)", setup=setup_statement + "map_nested_1line", number=100))
print(timeit.timeit("map_nested(test_array)", setup=setup_statement + "map_nested", number=100))
>>> 1.8096923486838081 # Без рекурсии
>>> 0.9477624593712808 # Однострочник
>>> 1.827232542223693 # Большая функция
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)
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)
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
解决方案:
测试:
很难发明一些东西——一切似乎都在你的决定中。但是,您可以尝试在一行中进行一些(不必要的?)更改 + 一个函数。如果需要,也可以将大型函数放在 1 行中,但这已经太多了:
更新: 早上比晚上更明智,经过深思熟虑,我想到了这个函数的非递归版本。非递归是好的,因为它不会在具有递归长度限制的操作系统上的长而重度嵌套列表上崩溃。坏消息是它非常慢。非递归解决方案的本质是将嵌套列表的进入和退出顺序输入到一个特殊的数组中。我们找到一个嵌套数组 - 我们输入一个指向它的指针 + 特殊的索引。贮存。我们离开它 - 我们提取指针和索引。
还有一些测试:
要在不创建新列表的情况下就地修改(深度优先搜索 (DFS)):
在这里
isatom(),谓词确定什么是给定算法的不间断元素(原子):为(深度嵌套的)列表中的每个原子apply_nested(func, lst)调用func一个函数lst。类似的解决方案flatten_gen():创建非递归变体很容易(如果使用广度优先搜索 (BFS)
deque.popleft()):例子:
同样,你可以定义返回新值而不改变输入的函数(DFS):
非递归选项:
例子:
立刻想到了这个决定。