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 ...
choose_cthat 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?random, likesample, instead of writing your own.choice_cso 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.choose()when the standard random module providesrandom.choice().