几天前,我偶然看到上周出版的“马特·帕克的数学难题”。这是一个简单的挑战:重新排列数字1-9(包括1-9),使每对顺序的数字之和为一个质数。
想出一个解决方案并不难。毕竟,原来的序列,
有很多对可以加到素数上,只需切换5和7就可以得到一个有效的序列:
更有趣的是想出所有的解决方案,人们很自然地认为这最好通过编写程序来完成。
我的第一个想法是使用暴力。生成列表的所有排列并遍历它们,只保留满足素数对标准的排列。当你需要一个快速的答案并且想要一个容易解释的解决方案时,暴力通常是最好的解决方案。
“快”是旁观者的眼神。一个聪明的算法无疑会运行得更快,但它的计算和调试时间可能比愚蠢的算法运行所需的时间要长得多。当程序必须一次又一次地运行时,聪明的算法是必不可少的,但是当您的程序是一次性的时,暴力通常是可行的。
在这种情况下,查看每个排列似乎都是可行的,因为只有9!=326,880种方法来排列这9个数字。而且我不需要想出一种方法来获得所有的排列,因为itertools库已经有了这样的函数。所以我开始写:
Python:1:从itertools导入置换2:3:#Initialization 4:Numbers=[1,2,3,4,5,6,7,8,9]5:Primes=[3,5,7,11,13,17]6:Winner=[]7:8:#循环排列,仅收集素数对9:对于置换中的p(Numbers):10:对于范围(1,9)中的i:11:如果p[i-1]+p[i]不在素数中:12:Break
在这一点上,我不得不停下来思考。问题是,BREAK语句只会使我脱离内循环,即从第10行开始的那个循环。但是,当我遇到一对没有加到质数上的对时,我想继续到外循环中的下一项,比如CONTINUE语句。中断和继续都无法指定中断的距离或继续的位置。Fortran的旧习惯开始在我的脑海中浮现,我开始希望有一个GOTO语句,但是Python没有这样的语句。1个。
一个笨拙的解决方案是添加一个标志变量来跟踪您是跳出了内部循环还是正常退出了它。
Python:1:from itertools导入排列2:3:#Initialization 4:Numbers=[1,2,3,4,5,6,7,8,9]5:PRIMES=[3,5,7,11,13,17]6:Winner=[]7:8:#循环排列,仅收集素数对9:对于置换(Numbers)中的p:10:allPrimes=True11:对于范围(1,9):12:If p[i-1]+p[i]不是素数:13:allPrimes=False14:Break 15:If allPrimes:16:winners.append(P)17:18:#打印结果19:print(len(获胜者))20:获奖者p:21:print(P)。
我知道我在做一个暴力解决方案,但是我有一些自豪感,我不喜欢这个样子。
几年前,内德·巴切尔德写了一篇关于打破两个循环的帖子,下面是他对这个方法的看法:
使用布尔变量记录循环已完成,并检查外部循环中的变量以执行第二个Break。这是一种低技术含量的解决方案,在某些情况下可能是正确的,但大多数情况下只是额外的噪音和簿记。
在这种情况下,我们并没有真正跳出两个循环,但是想法是一样的:额外的噪音和记账。
Batchelder的建议是使用一个函数来处理内循环,并简化外循环。就像这样:
Python:1:从itertools导入排列2:3:#Initialization 4:Numbers=[1,2,3,4,5,6,7,8,9]5:PRIMES=[3,5,7,11,13,17]6:Winner=[]7:8:def primePair:9:';';';s中的所有对都求和为素数吗?';';';10:对于范围(1,9)中的i:11:If s[i-1]+s[i]不是素数:12:返回False13:返回True14:15:#循环排列,只收集素数对16:对于p在排列(数字)中:17:If primePair(P):18:winners.append(P)19:20:#打印结果21:print(len(Winner))22:对于Winner中的p:23:Print(P)。
这个稍微长一点,但绝对更容易理解。我们应用了一些很好的老式结构化编程思想,将序列检查器放入它自己的功能中。很清楚它的作用,通过分离它,我们还使主循环(第16-18行)更加清晰。
但是Python还有另一个秘诀。在这个笨拙的解决方案中,我们使用了一个标志变量allPrimes来跟踪我们是正常退出循环还是通过中断退出循环。但是Python已经有一种方法可以做到这一点:for循环的Else子句。医生们是这样说的:
LOOP语句可以有ELSE子句;当循环因可迭代(WITH FOR)耗尽而终止时或当条件变为FALSE(WITH WHILE)时执行,但不会在循环由BREAK语句终止时执行。
我承认我以前从未用过这个,但它真的很简单:
Python:1:From itertools导入排列2:3:#Initialization 4:Numbers=[1,2,3,4,5,6,7,8,9]5:PRIMES=[3,5,7,11,13,17]6:Winner=[]7:8:#循环排列,仅收集素数对9:对于置换(Numbers)中的p:10:对于范围(1,9):11:如果p[i-1]+p[i]不是素数:12:Break 13:Else:14:Winners.append(P)15:16:#打印结果17:print(len(获胜者))18:对于获胜者中的p:19:print(P)。
如果我们在没有中断的情况下到达内部for循环的末尾,则执行Else子句中的winners.append(P)语句。如果我们在第11行遇到非素数和,则第12行的中断会使我们脱离整个for/Else构造,并继续执行外部循环中的下一个排列。
我不确定我更喜欢函数解决方案还是这个解决方案。我的本能倾向是使用较短的代码,但我觉得这不是描述该代码所做的事情的合适词语。事实上,我的第一反应是认为Else子句是在循环未正常完成时调用的,这与它的行为正好相反。
无论我是否会再次使用for/Else编写代码,知道它的存在以及如何使用它都是件好事。
如果您想知道,有140种方法可以对列表进行重新排序,以使所有顺序对之和为一个质数。你可能会争辩说,实际上只有70个,其他70个是相反的相同序列。我不会偏袒任何一方,我只是在这里磨练我的蟒蛇技能。
至少有几个第三方库提供GOTO,但我不想求助于它们。(↩