0

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
Recessive
  • 1,780
  • 2
  • 14
  • 37
  • 2
    " I would like to do is modify a single one of any of these numerical values in the list". OK and? What's the problem? – Julien Feb 27 '19 at 05:27
  • I edited the question. The wording was ambiguous, but understandable if you re-read it. Maybe try that next time. – Recessive Feb 27 '19 at 07:22
  • 1
    It was not ambiguous, it was totally unspecified. You can't expect people to read your mind... – Julien Feb 27 '19 at 07:43

1 Answers1

2

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]]]]]]]]]
Ralf
  • 16,086
  • 4
  • 44
  • 68
  • Careful: it might be really slow for very big nested lists, as the list of all the indexes is being generated. I'm sure there are optimisation that could be made, but this is more of a proof of concept than a fleshed out version. – Ralf Feb 27 '19 at 10:43
  • I came up with the same solution. This isn't slow at all if you compute `index_list` only once and leave it out of the `modify` function or pass it as argument... which is ok if all that happens is modifying values in the list, not its structure... – Julien Feb 27 '19 at 14:10
  • @Julien Thats a neet idea, building the `index_list` once and make several (random, as many as the author desires) selections of indexes to be modified. – Ralf Feb 27 '19 at 14:13
  • This is the other way I thought it could be solved, I just hoped there was some really easy way of doing this. The other way of doing this that would take at most O(n) time that I thought of doing (where n is the size of the list as a string) is to convert the list to a string, then find the location of all the numbers in the string, then modify randomly. – Recessive Feb 28 '19 at 03:40