Recessive
Recessive

Reputation: 1939

Modify a random value of a list at any point regardless of depth

I have a list that can be of any depth or length. By this I mean I could have a list like so:

lst = [1,2,3]

Or:

lst = [[2,233],[[[4,5],[66.33]],[[24,88.65,103,2200.0],[-44.2,-8,5]]], [[[[[[[[5]]]]]]]]]

and what I would like to do is randomly modify a single one of any of these numerical values in the list. I'm aware I could do something dodgy by converting the list to a string, but if there is a standard way of doing this, an answer pertaining to such would be appreciated!

Edit:

For those unaware, you cannot simply randomly select any of these values and modify it (as an example, say, add 1 to it), as the list could be nested. Here is an example of the input and output I am trying to get:

lst = [[2,233],[[[4,5],[66.33]],[[24,88.65,103,2200.0],[-44.2,-8,5]]], [[[[[[[[5]]]]]]]]]
lst = modify(lst,4) # Where 4 is the amount to add to a random number in the list

>lst: [[2,233],[[[4,9],[66.33]],[[24,88.65,103,2200.0],[-44.2,-8,5]]], [[[[[[[[5]]]]]]]]] 
# the fourth number to appear left-to-right in the list 5 has had 4 added to it, ultimately resulting in 9
# this number was randomly selected

Running the same code again, with the lst now updated:

lst = modify(lst,-2)
>lst: [[2,233],[[[4,9],[66.33]],[[24,86.65,103,2200.0],[-44.2,-8,5]]], [[[[[[[[5]]]]]]]]]
# The seventh number 88.65 has had 2 subtracted from it, to ultimately equal 86.65

Upvotes: 0

Views: 75

Answers (1)

Ralf
Ralf

Reputation: 16505

The first issue is to iterate over the list in order, no matter how deep the nesting goes. Here is an generator that returns just that (inspired by this answer):

import functools
import operator

def iter_nested_list(input_list):
    # build index of first level elements
    index_list_to_check = [(i, ) for i in range(len(input_list))]

    while len(index_list_to_check) > 0:
        current_index = index_list_to_check.pop(0)

        # get the element
        elem = functools.reduce(operator.getitem, current_index, input_list)

        if isinstance(elem, list):
            for i in range(len(elem)):
                # this is a list, so we need to check one level deeper
                index_list_to_check.append(current_index + (i, ))
        else:
            # this is not a list, so we yield the index
            yield current_index

This can be used like this:

>>> list_1 = [[2,233],[[[4,5],[66.33]],[[24,88.65,103,2200.0],[-44.2,-8,5]]],[[[[[[[[5]]]]]]]]]
>>> iter_nested_list(list_1)
<generator object iter_nested_list at 0x7fdbbc29d990>
>>> list(iter_nested_list(list_1))
[(0, 0), (0, 1), (1, 0, 0, 0), (1, 0, 0, 1), (1, 0, 1, 0), (1, 1, 0, 0), (1, 1, 0, 1), (1, 1, 0, 2), (1, 1, 0, 3), (1, 1, 1, 0), (1, 1, 1, 1), (1, 1, 1, 2), (2, 0, 0, 0, 0, 0, 0, 0, 0)]

To get a single element from the list, we can use the yielded indexes:

>>> index_list = list(iter_nested_list(list_1))
>>> index = index_list[1]
>>> index
(0, 1)
>>> functools.reduce(operator.getitem, index, input_list)
233

Now, to modify an element:

def modify(input_list, value_to_add):
    index_list = list(iter_nested_list(list_1))
    index = random.choice(index_list)

    index_base = index[:-1]    # list of all elements from 'index' excluding the last one
    index_elem = index[-1]     # single element, the last of the list 'index'

    # get list that holds the value we randomly selected
    sub_list = functools.reduce(operator.getitem, index_base, input_list)

    # modify value
    sub_list[index_elem] += value_to_add

And here it is in action:

>>> list_1 = [[2,233],[[[4,5],[66.33]],[[24,88.65,103,2200.0],[-44.2,-8,5]]],[[[[[[[[5]]]]]]]]]
>>> modify(list_1, 5)
>>> list_1
[[2, 233], [[[4, 5], [66.33]], [[24, 88.65, 103, 2200.0], [-44.2, -8, 10]]], [[[[[[[[5]]]]]]]]]
>>> modify(list_1, 5)
>>> list_1
[[2, 233], [[[4, 5], [66.33]], [[24, 88.65, 103, 2205.0], [-44.2, -8, 10]]],  [[[[[[[[5]]]]]]]]]
>>> modify(list_1, 5)
>>> list_1
[[2, 233], [[[4, 5], [66.33]], [[24, 88.65, 103, 2205.0], [-39.2, -8, 10]]], [[[[[[[[5]]]]]]]]]

Upvotes: 2

Related Questions