bendl
bendl

Reputation: 1630

How to pythonically select a random index from a 2D list such that the corresponding element matches a value?

I have a 2D list of booleans. I want to select a random index from the the list where the value is False. For example, given the following list:

[[True, False, False],
 [True, True, True],
 [False, True, True]]

The valid choices would be: [0, 1], [0, 2], and [2, 0].

I could keep a list of valid indices and then use random.choice to select from it, but it seems unpythonic to keep a variable and update it every time the underlying list changes for only this one purpose.

Bonus points if your answer runs quickly.

Upvotes: 1

Views: 1258

Answers (3)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476699

We can use a oneliner like:

import numpy as np
from random import choice

choice(np.argwhere(~a))

With a the array of booleans.

This works as follows: by using ~a, we negate the elements of the array. Next we use np.argwhere to construct a k×2-array: an array where every row has two elements: for every dimension the value such that the corresponding value has as value False.

By choice(..) we thus select a random row. We can however not use this directly to access the element. We can use the tuple(..) constructor to cast it to a tuple:

>>> tuple(choice(np.argwhere(~a)))
(2, 0)

You can thus fetch the element then with:

t = tuple(choice(np.argwhere(~a)))
a[t]

But of course, it is not a surprise that:

>>> t = tuple(choice(np.argwhere(~a)))
>>> a[t]
False

Upvotes: 4

Brandon Deo
Brandon Deo

Reputation: 4305

Assuming you don't want to use numpy.

matrix = [[True, False, False],
          [True, True, True],
          [False, True, True]]

valid_choices = [(i,j) for i, x in enumerate(matrix) for j, y in enumerate(x) if not y]
random.choice(valid_choices)

With list comprehensions, you can change the if condition (if not y) to suit your needs. This will return the coordinate that is randomly selected, but optionally you could change the value part of the list comprehension (i,j) in this case to: y and it'd return false, though thats a bit redundant in this case.

Upvotes: 1

Robᵩ
Robᵩ

Reputation: 168626

My non-numpy version:

result = random.choice([
    (i,j)
    for i in range(len(a)) 
    for j in range(len(a[i])) 
    if not a[i][j]])

Like Willem's np version, this generates a list of valid tuples and invokes random.choice() to pick one.

Alternatively, if you hate seeing range(len(...)) as much as I do, here is an enumerate() version:

result = random.choice([
    (i, j)
    for i, row in enumerate(a)
    for j, cell in enumerate(row)
    if not cell])

Upvotes: 3

Related Questions