2

I want to (pseudo)randomly select an object from a list that fits a criterion. I have a function that does this for one criterion I need:

from random import randint
def choose(array):
    return array[randint(0,len(array)-1)]
def choose_c(array):
    result = 'r'
    while result[-1] == 'r':
        result = choose(array)
    return result

But each time I want a new criterion I need to write a new function. Is there a simpler way?

6
  • Do all of your criteria fit the same pattern? That is, could you just add an argument to choose_c that reflects which symbol you want to reject? In your example you draw samples until you reject 'r'. Is that the only sort of constraint you will place on what can be drawn, or will the categorical type of constraint change and include different functionality? Commented Oct 7, 2014 at 23:49
  • Also, if you are able, consider using some of the other functions in random, like sample, instead of writing your own. Commented Oct 7, 2014 at 23:51
  • No the criteria vary checking different letters and different patterns of letters. Commented Oct 8, 2014 at 0:03
  • Then, as per my solution below, it's a good idea to write each criterion as a separate function, but to modify choice_c so that it can accept these extra functions as arguments. That way, the basic logic doesn't have to change, and you can plug in new criteria functions as needed. By writing them as different functions, you make the code more modular and reusable, whereas including them as one big chunk of code in a single function would make it harder to change or extend later. This also makes testing easier. Commented Oct 8, 2014 at 0:10
  • I'm curious why you are using your own choose() when the standard random module provides random.choice(). Commented Oct 8, 2014 at 2:45

2 Answers 2

2

One approach is to make your choice_c function accept a callable, which will be evaluated on the randomly sampled result until it evaluates to False, at which point the random sample will be returned.

def choice_c(array, criterion=None):
    result = choose(array)
    if criterion:
        while criterion(result):
            result = choose(array)

    return result

def r_criterion(some_sample):
    return some_sample == "r"

def gt5_criterion(some_sample):
    return some_sample > 5

choice_c(array, r_criterion)
choice_c(array, gt5_criterion)

This can also be accomplished using dropwhile and/or takewhile from the itertools module, and if this will be used heavily it might be worth making the choose function behave as a generator to use this implementation.

from itertools import dropwhile

def choose(array):
    while True:
        yield array[randint(0,len(array)-1)]

def choice_c(array, criterion=None):
    gen = choose(array)
    return next(dropwhile(criterion, gen)) if criterion else next(gen)

Of course, in both cases, this places the burden on you to write good unit tests, or otherwise ensure that the criterion functions make sense on the array contents, and that any errors are handled correctly, and that you don't loop over an infinite generator in the while section ...

Sign up to request clarification or add additional context in comments.

Comments

1

Thank you. I found the answer I was looking for:

from random import randint
def choose(array):
    return array[randint(0,len(array)-1)]
array = ['foo','bar','baz']
print choose([i for i in array if i[-1] != 'r'])

I filter out all the entries that don't fit the criteria and choose from the remaining.

1 Comment

While this will work, I urge caution. The argument you feed to your choose function here is a newly constructed list. If you need to draw samples from it repeatedly, you'll either have to pay the cost to construct the pre-filtered list (as in your solution) or have a slightly less efficient sampling method that involves occasionally rejecting some of the elements (as in mine). You just need to be aware about the tradeoffs between the two in the context you are working in. But yes, for small problems, with limited patterns to filter out, this will work just fine.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.