问题
I wrote a simple script to solve a "logic puzzle", the type of puzzle from school where you are given a number of rules and then must be able to find the solution for problems like "There are five musicians named A, B, C, D, and E playing in a concert, each plays one after the other... if A goes before B, and D is not last ... what is the order of who plays when?" etc.
To evaluate possible solutions, I wrote each "rule" as a separate function which would evaluate if a possible solution (represented simply as a list of strings) is valid, for example
#Fifth slot must be B or D
def rule1(solution):
return solution[4] == 'B' or solution[4] == 'D'
#There must be at least two spots between A and B
def rule2(solution):
returns abs(solution.index('A') - solution.index('B')) >= 2
#etc...
I'm interested in finding the Pythonic way to test if a possible solution passes all such rules, with the ability to stop evaluating rules after the first has failed.
At first I wrote the simplest possible thing:
def is_valid(solution):
return rule1(solution) and rule2(solution) and rule3(solution) and ...
But this seemed rather ugly. I thought perhaps I could make this read a bit more elegant with something like a list comprehension...
def is_valid(solution)
rules = [rule1, rule2, rule3, rule4, ... ]
return all([r(solution) for f in rules])
... but then I realized that since the list comprehension is generated before the all()
function is evaluated, that this has the side effect of not being short-circuited at all - every rule will be evaluated even if the first returns False
.
So my question is: is there a more Pythonic/functional way to be able to evaluate a list of True
/False
expressions, with short-circuiting, without the need to write out a long list of return f1(s) and f2(s) and f3(s) ...
?
回答1:
Use a generator expression:
rules = [ rule1, rule2, rule3, rule4, ... ]
rules_generator = ( r( solution ) for r in rules )
return all( rules_generator )
Syntactic sugar: you can omit the extra parentheses:
rules = [ rule1, rule2, rule3, rule4, ... ]
return all( r( solution ) for r in rules )
A generator is (basically) an object with a .next()
method, which returns the next item in some iterable. This means they can do useful things like read a file in chunks without loading it all into memory, or iterate up to huge integers. You can iterate over them with for
loops transparently; Python handles it behind-the-scenes. For instance, range
is a generator in Py3k.
You can roll your own custom generator expressions by using the yield
statement instead of return
in a function definition:
def integers():
i = 0
while True:
yield i
and Python will handle saving the function's state and so on. They're awesome!
来源:https://stackoverflow.com/questions/3405794/is-there-a-python-idiom-for-evaluating-a-list-of-functions-expressions-with-shor