1

I have a function that traverses a list and prints the element whenever it is an integer as follows:

def fnc(s, d = 0, lst=[0]):
    if isinstance(s, (tuple, list)):
        for i, elt in enumerate(s):
            if isinstance(elt, int):
                # print(f'{s}, {d}, {i}, {lst}')
                daccess(s, d, i, lst)
            else:
                lst.append(i)
                fnc(elt, d+1, lst)
                lst.pop(-1)

daccess is defined as follows:

def daccess(lst, d, i, indices):
    if d == 0:
        print(lst[i])
    else:
        lst_index = lst[indices.pop(1)]
        daccess(lst_index, d-1, i, indices)

fnc is supposed to pass s, d, i, lst as arguments to daccess where s is the nested list(s), d is the depth of the element to access, i is its index position and lst is a trace of every nested list's index encountered. For example with (s=[1,2,[3,[4,5,6]]], d=2, i=1, lst=[0,2,1]) can be translated to s[2][1][1] == 5.

When I pass the arguments printed by fnc on line 5 (which is commented out) to daccess, it works as expected:

daccess(s, 0, 1, [0])
daccess(s, 0, 0, [0])
...
daccess(s, 1, 0, [0, 8])
daccess(s, 1, 1, [0, 8])
etc...

However, when calling daccess inside fnc (so just calling fnc(s) I get this error:

1
2
33
6425
64
Traceback (most recent call last):
  File "fa.py", line 56, in <module>
    fnc(s)
  File "fa.py", line 20, in fnc
    fnc(elt, d+1, lst)
  File "fa.py", line 17, in fnc
    daccess(s, d, i, lst)
  File "fa.py", line 8, in daccess
    lst_index = lst[indices.pop(1)]
IndexError: pop index out of range

s is defined as follows: s = ( 1, 2, 33,6425, ( 3, 4, 44, ( 5, 66, 63225, 25673547, 88 ), ( 64,121 ) ), 9, 10, 11, ( 89, 90 ) )

(I know this is a tuple of tuples but just assume it's a list of lists. I have a separate function to convert a tuple of tuples to a list of lists before calling anything else)

I tried using a deep copy of s and indices but i get the same error back. I know for sure that if i call just daccess in the main file with the correct arguments it works but I really don't know what goes wrong when calling it inside fnc.

Any help as to why this happens is appreciated.

alexandrosangeli
  • 262
  • 2
  • 13
  • Try replacing the default arg `lst=[0]` by `lst=None` and then in the body `if lst is None: lst = [0]`. lst is only generated once when it is created and you are popping it twice. – Robin Gertenbach Aug 31 '21 at 09:03
  • @RobinGertenbach I still get the exact same error. The thing is that, it works when calling just `daccess` in the main file with the correct arguments for any element I try to access. The problem is when calling it inside `fnc`. – alexandrosangeli Aug 31 '21 at 09:11
  • Ah, yes, I see. What's your expected output? e.g. Is the 64 desired? or would you instead want it to continue 9, 10, 11? – Robin Gertenbach Aug 31 '21 at 09:37
  • Well, `fnc` is supposed to go through all integers in `s` so I am expecting to go with the same order as `s` in my question. i.e. 1, 2, 33, 6425...89, 90 – alexandrosangeli Aug 31 '21 at 09:42
  • Related question: [Flatten an irregular list of lists](https://stackoverflow.com/questions/2158395/flatten-an-irregular-list-of-lists). You can write `flatten`, call `flatten` on your nested list, then print the result. – Stef Aug 31 '21 at 09:46
  • @Stef That's a good approach. Unfortunately I printing the elements is just a "dummy" function there. What I actually want to do is keep the same structure and replace the integers with a string representation (i.e 4 --> '4') – alexandrosangeli Aug 31 '21 at 09:49

2 Answers2

1

Based on your comment you want to keep the list structure intact but perform some operation (str) on each non-iterable element (inferred).

How about this avoiding all the popping and indexing?

from typing import Iterable
def fnc(s):
  if isinstance(s, Iterable) and not isinstance(s, (str, bytes)):
    return [fnc(e) for e in s]
  else:
    return str(s)
Robin Gertenbach
  • 10,316
  • 3
  • 25
  • 37
  • That's a neat solution that can be modified a bit to fit my actual needs thank you! I would have accepted the answer but it doesn't answer the original question which still bothers me. Nevertheless thank you, I can work with this too. – alexandrosangeli Aug 31 '21 at 10:15
1

Inspired by the related question Flatten an irregular list of lists.

from collections.abc import Iterable

def map_in_depth(f, l):
  return [(map_in_depth(f, e) if isinstance(e, Iterable) and not isinstance(e, (str,bytes)) else f(e)) for e in l]

Or alternatively, if you know that the list contains only ints and lists:

def map_in_depth(f, l):
  return [(f(e) if isinstance(e, int) else map_in_depth(f, e)) for e in l]

With both versions, I get:

map_in_depth(lambda x: 2*x, [1,2,[3,[4,5,6]]])
# [2, 4, [6, [8, 10, 12]]]
Stef
  • 13,242
  • 2
  • 17
  • 28