Answering this question , I became interested in a more universal solution ...

There is a list of arbitrary nesting, for example:

['1','2', ['1',['2','4',['5','6']]],'7','8'] 

It is necessary to apply the function to all elements of the list (including all nested), while maintaining its structure.

For example, convert all the elements into numbers and square them to make it:

 [1, 4, [1, [4, 16, [25, 36]]], 49, 64] 

I published my version of the solution, but I would be interested to see alternative (more interesting) solutions.

    4 answers 4

    To put the edit without creating new lists ( depth search — 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) 

    Here, the isatom() predicate determines what is an integral element (atom) for a given algorithm: apply_nested(func, lst) calls the func function for each atom in the (deep- apply_nested(func, lst) ) list lst . A similar solution: flatten_gen() .

    It's easy to create a non-recursive option ( search wide — breadth-first search (BFS), if you use 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) 

    Example:

     >>> 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] 

    Similarly, you can define functions that return new values ​​without changing the input (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] 

    Non-recursive option:

     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 

    Example:

     >>> map_nested(lambda atom: int(atom)**2, nested)) [1, 4, [1, [4, 16, [25, 36]]], 49, 64] 

      Decision:

       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))] 

      Test:

       In [154]: lst = ['1', '2', ['1', ['2', '4', ['5', '6']]], '7', '8'] In [155]: map_nested(lst, lambda x: int(x)**2) Out[155]: [1, 4, [1, [4, 16, [25, 36]]], 49, 64] 

        It’s hard to make something up - everything seems to be in your solution. However, you can try to make a couple (unnecessary?) Changes + function in one line. A large function can also be placed in 1 line, if desired, but this will already be too:

         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) 

        UPDATE: Morning is wiser than the evening and after thinking I gave birth to a non-recursive version of this function. Non-recursive is good because it does not fall on long and heavily nested lists on the OS with a limit on the length of the recursion . Bad that very slow. The essence of a non-recursive solution is that the order of entry and exit into nested lists is entered into a special array. We find the nested array - we enter a pointer to it + index in the spec. repository. We leave from it - the pointer and the index is retrieved.

         # 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) 

        Also a bit of testing:

         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 # Большая функция 
        • one
          Thank! Very interesting option - I study ...! - MaxU
        • 2
          @MaxU, added comments. I don’t even know where a non-recursive implementation might be needed, but if I started writing code, it becomes difficult to stop. - m9_psy
        • one
          it is always good to have an alternative option in case the recursive option for some reason will not work. - MaxU
        • one
          It looks complicated. A particularly non-recursive version can be greatly simplified - jfs

        The decision immediately came to mind

         def anon(l, func=lambda x: int(x) ** 2): for n, i in enumerate(l): if type(i) != list: l[n] = func(i) else: anon(i) return l lst = ['1', '2', ['1', ['2', '4', ['5', '6']]], '7', '8'] print(anon(lst)) >>> [1, 4, [1, [4, 16, [25, 36]]], 49, 64]