python 之玩转24点数字游戏

最近迷上了24点数字游戏,让四个数字在脑子里随意碰撞,结合,然后检查最后的结果,然后再碰撞,结合,检查,不断重复…到最后总会有一种方法让这四个数字乖乖的等于24。当然,前提是这四个数字得来自正规的24点游戏库,因为不是随便四个数字都能得到24点的。

在手机上的游戏软件上玩了数十关之后,我突发奇想,为何不把这活交给程序做做呢?由于最近一直在学习Python,就打算用Python玩玩这个游戏,看看结果如何。

我本着先怼出来为敬的原则(就是不考虑算法,不考虑效率,不考虑计算时间等等,先出结果再说)自然而然想到的就是穷举。一共四个数字,外加三个运算符(将四个数字整合成一个数字只须要三个运算符号即可,实际用到的运算符号有四个:加、减、乘、除),这些都是小整数,计算机穷举其实也费不了多大劲。

既然要穷举,那就要考虑到所有的情况。下面上一个“24点游戏万能公式”:
A ● B ● C ● D = 24 A●B●C●D = 24 ABCD=24
●代表任一运算符。我的思路是先给这四个数字一个全排列,也就是24种情况,然后每一个运算符来一个遍历,也就是64种情况,这样算来最多只要循环24x64次就能得到结果。

思路有了,然后开始编程。对于运算符号,我用的是operator模块中的add,sub,mul 和 truediv 函数,因为基于Python中所有函数都是一等对象的理念,把这四个函数放进一个列表里之后很容易实现遍历。实现全排列则用的是 itertools 模块中的 permutations 函数。好了,废话不多说,直接上程序:

from operator import add,sub,mul,truediv as div
from itertools import permutations

def pts_24(list_int_4):
	
	operator_4 = [add,sub,mul,div]
	ops = ['+','-','x','/']
	for ls in permutations(list_int_4,4):
		for oprt_1 in operator_4:
			for oprt_2 in operator_4:
				for oprt_3 in operator_4:
					try:      
						result = oprt_1(ls[0],oprt_2(ls[1],oprt_3(ls[2],ls[3])))
						result_22 = oprt_1(oprt_2(ls[0],ls[1]),oprt_3(ls[2],ls[3]))
						result_31 = 0
						if oprt_1 in [sub,div]:
							result_31 = oprt_1(oprt_2(ls[1],oprt_3(ls[2],ls[3])),ls[0])
					except ZeroDivisionError:   #除数是零的情况在这种全面的遍历下似乎不可避免
						pass                    #为了程序流畅以及输出美观,所以跳过 
						                        #PS:反正这种情况肯定不是我们想要的结果
					i = operator_4.index(oprt_1)#提取算术符号的索引,用于输出+-x/符号和加括号的逻辑判断
					j = operator_4.index(oprt_2)
					k = operator_4.index(oprt_3)
					bra1=''                     #设置括号,让输出格式符合算术逻辑
					bra2=''
					ket1=''
					ket2=''
					if result == 24:
						print('--24 points found--')
						if i>=1 and j<2 or i==3:
							bra1 = '('
							ket1 = ')'
						if j>=1 and k<2 or j==3:
							bra2 = '('
							ket2 = ')'
						return (str(ls[0])+ops[i]+bra1+str(ls[1])+ops[j]+
							bra2+str(ls[2])+ops[k]+str(ls[3])+ket2+ket1)
					elif result_31 == 24:
						print('--24 points found--')
						if j==3 and k==3 or j>1 and k<2:
							bra2 = '('
							ket2 = ')'
						if i==3 and j<2:
							bra1 = '('
							ket1 = ')'
						return (bra1+str(ls[1])+ops[j]+bra2+str(ls[2])+ops[k]+
							str(ls[3])+ket2+ket1+ops[i]+str(ls[0]))
					elif result_22 == 24:
						print('--24 points found--')
						if i>1 and j<2:
							bra1 = '('
							ket1 = ')'
						if i>=1 and k<2 or i==3 and k==3:
							bra2 = '('
							ket2 = ')'
						return (bra1+str(ls[0])+ops[j]+str(ls[1])+ket1+
							ops[i]+bra2+str(ls[2])+ops[k]+str(ls[3])+ket2)
	return("These four numbers can not be calculated to 24")


while True:
	
	str_4 = input("Please input 4 integers separated by ' '(q to quit):\n")
	if str_4 == 'q':
		break
		
	list_4 = str_4.split()
	list_int_4 = [int(i) for i in list_4]
	print(pts_24(list_int_4))


程序写好后,我先上了一组 ‘6666’ 测试了一下:

Please input 4 integers separated by ' '(q to quit):
6 6 6 6
--24 points found--
6+6+6+6

果然很顺利。我又打开24点游戏软件,上了一组“专业测试”,也都能一一通过。至此,我这个“先怼出来为敬的版本”算是大功告成了。

Please input 4 integers separated by ' '(q to quit):
4 5 6 11
--24 points found--
6x(5+11)/4
Please input 4 integers separated by ' '(q to quit):
12 5 12 3
--24 points found--
12x5-12x3
Please input 4 integers separated by ' '(q to quit):
12 12 3 9
--24 points found--
12/(3/9)-12
Please input 4 integers separated by ' '(q to quit):
5 5 1 1
--24 points found--
5x5-1x1
Please input 4 integers separated by ' '(q to quit):
7 7 7 12
--24 points found--
(7+7)/(7/12)
Please input 4 integers separated by ' '(q to quit):
5 1 3 1
--24 points found--
(5+1)x(3+1)

Please input 4 integers separated by ' '(q to quit):
1 1 1 1
These four numbers can not be calculated to 24
Please input 4 integers separated by ' '(q to quit):
88 88 88 88
These four numbers can not be calculated to 24

最后两种例外情况也都确证了不能找到使他们运算得到24的结果,但是到这里有人可能会说,慢着,你看这:(1+1+1+1)! 不就正好等于24吗?好啊,少侠有如此眼力,我的程序自然甘拜下风,所以我就只好在程序里加上一行这个了:(滑稽)

if A==B==C==D==1 or A==B==C==D==0:
	print('(A!+B!+C!+D!)! = 24')   #考虑到0,每个数字后面都加了阶乘

在写这个程序的过程中,中间也出了不少问题。我程序中计算的核心代码行:

result = oprt_1(ls[0],oprt_2(ls[1],oprt_3(ls[2],ls[3])))

运算的优先级是从后往前的,而不管运算符号如何,这也算是遍历(便利)中的一点小小不足吧。但是后面用上小括号,这个问题便迎刃而解了。这其中还有很多细节上的问题,我就不一一赘述了,对程序员来说,代码才是最重要的!

再会!

补充:

这个程序,从构思到完全写好总共花了我差不多两天的时间,其中是改版又改版,文中直接放的最终版本。而文章中的几则测试,可以说都是我前面几版程序的 ‘bug’ 所在。而最终版本(我自测了近百例没出问题)也正是在遇到下一个 bug 的路上,遇到 bug 也要保持微笑(?)。在写这个程序的时候,我觉得最关键的地方有两点:一个是计算,再一个是输出。计算主要是看程序是不是把所有的情况都考虑过了,这里的难点在于减法和除法不满足交换律,虽然全排列了,但这只是一个预处理,在计算过程中就会失去效果,所以在程序中我又添加了 result_31 和 result_22 。输出方面的难点在于如何合适的添加括号,拿这个例子来说:(7+7)/(7/12) 如果不添加括号,输出就会是这样: 7+7/7/12 按照正常计算得到的结果肯定不是24,而实际情况是,程序是通过带括号的式子的逻辑来计算的(具体原因可参考源码)所以结果是24。有一个简单的思路是根据源码的计算顺序都把括号加上,这样人按照算术规则计算就不会有问题了,但是这样的输出格式显得太死板,比如这个例子 12x5-12x3 两边完全不需要括号,加上括号就是画蛇添足,多此一举了。对于添加括号,主要是根据程序中的计算顺序参考四则运算符运算优先级来添加的。如果程序中的顺序和运算符优先级顺序有冲突,那么就加上括号解决冲突;如果没有冲突就不加括号。这里面的逻辑也很有意思,以后有时间我还会补充上。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值