3

I am looking for a nicer solution for changing a specific item in a nested list of unknown and varying depth.

Say, I have a list lst = ['a', 'b', [['c'], ['d', ['e', 'f']]] and I want to change the 'e' to an 'x'. I could do this by simply hard coding:

lst[2][1][1][0] = 'x'

But I'd have to use an unknown number of indices to change different items. Changing the 'c', e.g., would only require 3 indices.

To make it a bit more dynamic I've already written a function that returns the wanted indices of a certain item, in this case indices = get_indices('e', lst) would return the list [2, 1, 1, 0]

So my current solution is the following function:

def set_element(lst, index, value):
    levels = len(index)
    if levels == 1:
        lst[index[0]] = value
    elif levels == 2:
        lst[index[0]][index[1]] = value
    elif levels == 3:
        lst[index[0]][index[1]][index[2]] = value
    elif levels == 4:
        lst[index[0]][index[1]][index[2]][index[3]] = value
    elif levels == 5:
        lst[index[0]][index[1]][index[2]][index[3]][index[4]] = value
    elif levels == 6:
        lst[index[0]][index[1]][index[2]][index[3]][index[4]][index[5]] = value
    else:
        return False
    return True

Calling set_element(lst, indices, 'x') would do the trick.

But ... frankly ... I'm not quite happy with this solution, there has to be a smoother, pythonic way to achieve this. I'm probably missing something.

Can anybody think of an even more dynamic way where I don't have to predefine the maximum number of possible levels?

EDIT:

In the above example I've got the indices from the list I'd like to alter, which makes the method seem a bit overly complicated. In my specific case, however, I actually need to use the indices of an item to change another item in a different list of the very same structure.

So I have to insist on using the list of indices.

1
  • There is and it utilizes recursion. Googling it will get you far since your case is a very typical one. Commented Oct 19, 2017 at 9:42

3 Answers 3

4

Lists only accept integer indices. You can subclass list to overload the __getitem__ and __setitem__ to accept tuples and lists as indices. By overloadeding __init__, we can simplify variable creation.

class nestedlist(list):
    def __init__(self, *args):
        if len(args) == 1:
            super().__init__(nestedlist(arg) if type(arg) is list else arg for arg in args[0])
        else:
            super().__init__(nestedlist(arg) if type(arg) is list else arg for arg in args)

    def __getitem__(self, key):
        if type(key) is int:
            return super().__getitem__(key)
        if len(key) == 1:
            return super().__getitem__(key[0])
        return self.__getitem__(key[0])[key[1:]]

    def __setitem__(self, key, value):
        if type(key) is int:
            super().__setitem__(key, value)
            return
        if len(key) == 1:
            super().__setitem__(key[0], value)
            return
        self.__getitem__(key[0]).__setitem__(key[1:], value)

lst = nestedlist(['a', 'b', [['c'], ['d', ['e', 'f']]]])

lst[2,1,1,0] = 'x'
# or
indices = [2,1,1,0]
lst[indices] = 'x'
Sign up to request clarification or add additional context in comments.

Comments

3

How about this:

lst = ['a', 'b', [['c'], ['d', ['e', 'f']]]]

def change(seq, what, make):
  for i, item in enumerate(seq):
    if item == what:
      seq[i] = make
    elif type(item) == list:
      change(item, what, make)
  return lst

print(change(lst, 'c', 'k'))
#['a', 'b', [['k'], ['d', ['e', 'f']]]]

It's a bit specific for your case, but it works.

1 Comment

Unfortunately, I forgot to mention, that using the indices is quite crucial, so that you can change an item at the exact position of an item in a different list of the same structure. But apart from that it's definitely an very good solution.
2

I really believe that your function get_indices is already almost the answer to your problem - just when you find the element - change it! =)

but as an answer - use recursion (as you probably used in find_indices)

def set_element(lst, index, value):
    if(len(index)==1):
        lst[index[0]] = value
    else:
        set_element(lst[index[0]],index[1:],value)

1 Comment

That's true, I could have changed it when i found it. But, I'm afraid I forgot to mention, that using the indices of a specific item is crucial. Because then I can change an item at the same position in another list of the very same structure, which comes in quite handy... So your answer is excellent, thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.