Extracting values from non empty nested lists, randomly

Question:

I have the following list of objects from which I would like to extract random values.

l = [
    {'A':[],'B':['b1','b2'],'C':[],'D':['d1','d2','d3'],'E':['e1']},
    {'A':['a4','a5','a6'],'B':['b6'],'C':['c4','c5','c6'],'D':[],'E':['e4','e5','e6']},
    {'A':['a7'],'B':['b7','b8'],'C':['c7','c8','c9'],'D':['d7','d8','d9'],'E':[]},
]

The goal is:
For each of the 3 objects in list l, extract one nested value randomly.
The result should be a list with 3 values in it.
Examples of possible results :
res = [‘b1′,’b6′,’d9’]
or res = [‘e1′,’c4′,’a7’]

I have tried using random.choice() but the problem is when random lands on an empty list…
If it does land on an empty list such as ‘A’ in the first object, then it has to try again until it lands on a non empty list.

How can I achieve this?

Asked By: JK2018

||

Answers:

Equal probability for lists

You can do a random choice on keys that have a list with value, then a choice between these values.

To do that, you need to filter empty lists. It’s easy to do that with a list comprehension and a filter:

[v for v in o.values() if v]

You need to choose a random key among keys with value (the filter we talked before) and then, choose a value in the list of the selected key.

import random

l = [
    {'A':[],'B':['b1','b2'],'C':[],'D':['d1','d2','d3'],'E':['e1']},
    {'A':['a4','a5','a6'],'B':['b6'],'C':['c4','c5','c6'],'D':[],'E':['e4','e5','e6']},
    {'A':['a7'],'B':['b7','b8'],'C':['c7','c8','c9'],'D':['d7','d8','d9'],'E':[]},
]

res = [random.choice(random.choice([v for v in o.values() if v])) for o in l]
print(res)
# ['e1', 'b6', 'd7']

This solution works this way with probabilities. Let’s take the first dictionary:

{'A':[],'B':['b1','b2'],'C':[],'D':['d1','d2','d3'],'E':['e1']},

The first choice will choose between ["b1", "b2"], ["d1", "d2", "d3"] and ["e1"], with 1/3 chance for each. This mean that the final distribution will be:

1/3: [
    1/6: "b1", 
    1/6: "b2",
],
1/3: [
    1/9: "d1",
    1/9: "d2",
    1/9: "d3",
],
1/3 [
    1/3: "e1",
],

Equal probability for leafs

If you need equal probability of leaf values instead of equal probability for lists, you don’t need to filter as the flattening comprehension will pass over empty lists without doing anything:

res = [random.choice([e for v in o.values() for e in v]) for o in l]

Distribution of values with this code is:

1/3: [
    1/6: "b1", 
    1/6: "b2",
],
1/2: [
    1/6: "d1",
    1/6: "d2",
    1/6: "d3",
],
1/6 [
    1/6: "e1",
],

Each leaf value of the dict is equally distributed.

Answered By: Dorian Turba

You could flatten the lists and then pick –

import random
from itertools import chain
lvals = [inner_dict.values() for inner_dict in l]
lvals_flattened = [list(chain.from_iterable(lval)) for lval in lvals]
random_choice = [random.choice(flattened) for flattened in lvals_flattened]

Output

['b2', 'a6', 'c7']
Answered By: Mortz
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.