8

我写了一个简单的脚本来解决“逻辑谜题”,这是一种来自学校的谜题类型,你被赋予了许多规则,然后必须能够找到解决诸如“有五个音乐家叫 A、B、C 、D、E在音乐会上演奏,一个接一个地演奏……如果A在B之前,而D不是最后……谁在什么时候演奏的顺序是什么?” 等等

为了评估可能的解决方案,我将每个“规则”编写为一个单独的函数,该函数将评估可能的解决方案(简单地表示为字符串列表)是否有效,例如

#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...

我有兴趣找到 Pythonic 的方法来测试一个可能的解决方案是否通过了所有这些规则,并且能够在第一个失败后停止评估规则。

起初我写了最简单的东西:

def is_valid(solution):
    return rule1(solution) and rule2(solution) and rule3(solution) and ...

但这似乎相当丑陋。我想也许我可以通过列表理解之类的东西使这个阅读更加优雅......

def is_valid(solution)
    rules = [rule1, rule2, rule3, rule4, ... ]
    return all([r(solution) for f in rules])

...但后来我意识到,由于列表理解是在all()评估函数之前生成的,因此这具有根本不会短路的副作用 - 即使第一个规则返回,也会评估每个规则False

所以我的问题是:是否有一种更 Pythonic/功能性的方式能够评估True/False表达式列表,短路,而不需要写出一长串return f1(s) and f2(s) and f3(s) ...

4

1 回答 1

13

使用生成器表达式

rules = [ rule1, rule2, rule3, rule4, ... ]
rules_generator = ( r( solution ) for r in rules )
return all( rules_generator )

语法糖:您可以省略额外的括号:

rules = [ rule1, rule2, rule3, rule4, ... ]
return all( r( solution ) for r in rules )

生成器(基本上)是具有.next()方法的对象,该方法返回某个可迭代对象中的下一项。这意味着他们可以做一些有用的事情,比如分块读取文件而不将其全部加载到内存中,或者迭代到巨大的整数。for您可以使用循环透明地迭代它们;Python 在幕后处理它。例如,range是 Py3k 中的生成器。

您可以使用yield语句而不是return函数定义来滚动您自己的自定义生成器表达式:

def integers():
    i = 0
    while True:
        yield i

Python 将处理保存函数的状态等。他们太棒了!

于 2010-08-04T13:11:57.537 回答