Reputation: 1939
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
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