本文主要是介绍当一个准大一心血来潮,开始写24点……,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
大家好!
我是一个准大一的编程小白🥬,会一点Python的基础语法,最近正在学习C语言。
这是我第一次发表文章,大家看着玩就好了😝(希望看了我的文章,大家能多干两碗饭😝)
如果能给我一些建议或者提供一些想法,那就再好不过了!
昨天和我妈玩24点,我被薄纱了。。。
于是我心里萌生了这样的想法:
我要用程序强行破解24点!
立刻开干!
构思
# 24点规则:任意抽取4张牌(称牌组),用加、减、乘、除(可加括号)把牌面上的数算成24
1.确定大方向
我觉得破解24点的方法无非三个:
①正面迎敌
②正难则反
③出奇制胜
鉴于这个项目计算量较小,暴力枚举就完了
所以我的想法是正面迎敌,也就是暴力枚举所有可能的运算方式,计算其结果,查找有无等于24的
2.确定运算逻辑
大致方向已经确定,现在再来想想怎么计算
我将24点的计算分成两类——组合与排列
1) 组合
将四个数字分别记作a,b,c,d,两个数之间的一切运算记作 f (x, y)
那么组合的运算特点就是:
m = f (a, b) #为了更好地表示,我将这两步称作“一级组合运算”
n = f (c, d)
最终结果 = f (m, n) # 而这一步称作“二级组合运算”
2) 排列
继承上面的设定
排列的运算特点可以表示为:
m = f (a, b)
n = f (c, m)
最终结果 = f (d, n)
好像没有运算能逃出这两种情况了,那就这么分类了!
编程
本人第一次写这么长的代码,为了便于思考同时使代码更加整洁,我写程序的时候把代码分成了几个大块并用自定义函数写,这样似乎对我的思路很友好,而且还避免了代码复现,一举多得!
下面的代码就按照大块分割,并尽量还原了我当时写代码时的思考。
注意:我是先解决组合运算逻辑,再解决排列运算逻辑的!
1.牌组的初始化
card = [int(input("请输入数字(1~10):")) for _ in range(4)]
print('\n四张牌如下:')
for i in card:print(str(i), end='\t')
output = [] #盛装正确的运算式
2.定义一个超级运算函数calc(int, int)
calc函数可以将两个数字的所有运算结果放到一个列表里,顺序是加、减、乘、除
def calc(left, right): # 列举所有运算的结果add = left + rightminus1 = left - rightminus2 = right - lefttimes = left * rightdivision1 = left / rightdivision2 = right / leftreturn [add, minus1, minus2, times, division1, division2]
问题1:因为减法和除法这两个双目运算符具有方向性,为此我就得往列表里放A-B,B-A 和 A/B,B/A!这样就增大了calc函数返回的列表的复杂度(6个元素)!
解决办法1:判断calc函数两个参数的大小,使得总是大减小和大除以小,这样是不影响组合运算的,但有一个小缺点——损失了一部分正确的运算式,但是摒弃了负数和介于0~1的分数,这样更加符合人的思维(好像大家玩24点的时候很少用到负数和分数吧?)。好,这样问题1就当做解决了!
问题2:写完了才想到一级组合运算的结果可能为0,那二级组合运算的时候0就有可能作为分母,进而导致ZeroDivisionError !
解决办法2:当小值为0的时候,让division的输出结果为-1;同时calc函数再遇到-1的时候不要计算,而是也直接输出-1
改进版
def calc(left, right): # 列举所有运算的结果if left == -1 or right == -1:ret = -1else:if left < right:left, right = right, leftadd = left + rightminus = left - righttimes = left * rightif right == 0:division = -1else:division = left / rightret = [add, minus, times, division]return ret
3.为二级组合运算定义一个运算函数combine(list, list)
combine函数可以将两个calc结果各抽出一个元素来,进行calc运算,并将结果放到列表中
返回的列表是一个16x4的二维数组
def combine(one, two): # 将两个calc结果进行calc运算,输出所有可能(遇到-1不运算)result = []for i in range(4):for j in range(4):result.append(calc(one[i], two[j]))return result # result是16行4列的二维数组
太好了,这个部分非常顺利,没有遇到大问题!
4.定义一个查找有无24的函数check(array)
check函数可以遍历combine的结果——一个二维数组,检查有没有目标 24;如果有的话,就把它的位置放到列表里
还记得在写calc函数的时候,会产生一些垃圾结果—— -1 吗?对,它也在这个二维数组中,因为combine函数就是一个复杂版calc函数。所以,实际上这个二维数组可能长这样(1、2、3、4表示可能的结果):
[ [1,2,3,4],[1,2,3,4], -1 ,[1,2,3,4],[1,2,3,4], -1 ,[1,2,3,4],[1,2,3,4],[1,2,3,4], -1 , -1 ,[1,2,3,4],[1,2,3,4],[1,2,3,4], -1 ]
所以,在遇到了 -1 的时候要跳过,也就是continue
所以,代码如下:
#输入一个combine结果(二维数组),检验是否有24;若有,返回其在二维数组中的位置
def check(result):position = []for i in range(16):if result[i] == -1:continueelse:for j in range(4):if result[i][j] == 24:position.append([i, j])return position # 返回24的位置(i行j列),以便回推运算式
5.定义一个可以确定运算符的函数expression(list)
expression函数可以通过check函数的结果——24的位置,来确定运算符是哪一种,并放到列表中!
其实原理并不复杂,因为calc函数输出的结果是按照加、减、乘、除的顺序排列的,所以combine函数的结果——16x4的二维数组,它是有特点的!
不妨写一下这个二维数组大致的结构:
两个一级组合运算的符号(下) 二级组合运算的符号(右) | + | - | * | / |
---|---|---|---|---|
+ + | 某结果 | 某结果 | 某结果 | 某结果 |
+ - | 某结果 | 某结果 | 某结果 | 某结果 |
+ * | 某结果 | 某结果 | 某结果 | 某结果 |
+ / | ... | ... | ... | ... |
- + | ||||
- - | ||||
- * | ||||
- / | ||||
* + | ||||
* - | ||||
* * | ||||
* / | ||||
/ + | ||||
/ - | ||||
/ * | ||||
/ / |
先假设输入的表示位置的参数(即形参)名称为position
写一个用于查找符号的列表
sym = ['+', '-', '*', '/']
这样,
第一个一级组合运算符就可以这样表示:
symbol_one = position[i][0] // 4
第二个一级组合运算符可以这样表示:
symbol_two = position[i][0] % 4
而二级组合运算符可表示为:
symbol_three = position[i][1]
好了,最终代码如下:
def expression(position): # 通过位置信息返回运算符sym = ['+', '-', '*', '/']exp = []for i in range(len(position)):symbol_one = position[i][0] // 4symbol_two = position[i][0] % 4symbol_three = position[i][1]exp.append([sym[symbol_one], sym[symbol_two], sym[symbol_three]])return exp
6.组合运算函数的诞生
组合运算函数可以将四张手牌进行组合运算,最终输出目标的运算式
这个函数集合了calc函数、combine函数、check函数、expression函数
因为它是我先写的运算逻辑,所以不妨就叫它method_1函数吧!
在这个函数中,我要完成以下目标:
①判断是否有解
②将输入的四张牌调整到正确的顺序
③输出最终的表达式
我将四张牌分别命名为card1,card2,card3,card4
先完成目标①:
只要看check函数的返回值是否为空即可
所以我用exist来存储check函数的结果,后面再去判断
exist = check(combine(calc(card1, card2), calc(card3, card4)))
完成目标②:
我们之所以要调整数字的位置,是因为calc函数里将大的数字放在左边,小的放在右边,而这种交换的作用域是calc函数的内部,换句话说就是只在calc函数内部进行的,而位于函数外部的实参并不交换它们的值
所以现在我们需要将数字的位置调整至与calc函数内部相同
根据calc函数,我们只需要使
card1 > card2
card3 > card4
if card1 < card2:card1, card2 = card2, card1
if card3 < card4:card3, card4 = card4, card3
完成目标③:
如果exist非空,则把exist输入给expression函数,再用返回的符号构建表达式字符串,并放到output列表(用于盛放最终答案)中;如果exist为空,则不操作
if exist:exp = expression(exist)for i in range(len(exp)):output.append('('+str(card1)+exp[i][0]+str(card2)+')'+exp[i][2]+'('+str(card3)+exp[i][1]+str(card4)+')=24')
好,剩下将代码整合即可,结果如下:
def method_1(card1, card2, card3, card4): # 输出方法一(组合)的结果(算式)if card1 < card2:card1, card2 = card2, card1if card3 < card4:card3, card4 = card4, card3exist = check(combine(calc(card1, card2), calc(card3, card4)))if exist:exp = expression(exist)for i in range(len(exp)):output.append('('+str(card1)+exp[i][0]+str(card2)+')'+exp[i][2]+'('+str(card3)+exp[i][1]+str(card4)+')=24')
至此,组合这一运算逻辑的代码算是敲完了!
7.为排列运算定义一个运算函数arrange(int, int, int, int)
arrange函数可以完成排列的运算逻辑
操作不难,代码如下:
def arrange(card1, card2, card3, card4): # 类似combine函数result = []for i in range(4):for j in range(4):res1 = calc(card1, card2) # 一维数组res2 = calc(res1[i], card3) # 二维数组(4*4)res3 = calc(res2[j], card4) # 二维数组(16*4)result.append(res3)return result # result是16行4列的二维数组
arrange函数的结果也可以传给check函数进行处理(同combine函数)
8.排列运算函数的诞生
排列运算函数可以将四张手牌进行排列运算,最终输出目标的运算式
这个函数集合了arrange函数、check函数、expression函数
因为它是我后写的运算逻辑,所以叫它method_2函数!
在这个函数中,我要完成以下目标:
①判断是否有解
②将输入的四张牌调整到正确的顺序
③输出最终的表达式
我将四张牌分别命名为card1,card2,card3,card4
先完成目标①:
方法和刚才一样,只要看check函数的返回值是否为空即可
所以我用exist来存储check函数的结果,后面再去判断
exist = check(arrange(card1, card2, card3, card4))
if exist:exp = expression(exist)
完成目标②:
根据arrange函数中对calc函数的调用情况,需要让
1) card1 > card2
2) calc(card1, card2) > card3
3) calc(calc(card1, card2), card3) > card4
1) 没什么好分析的了,跟刚才一样就可以了,直接 Ctrl+C, Ctrl+V 献上
if card1 < card2:card1, card2 = card2, card1
2) 这个部分我的操作可能繁冗了
我先创建了一个用于符号转数字的字典
rev_dic = {'+': 0, '-': 1, '*': 2, '/': 3}
然后用cv表示calc(card1, card2)的值
用str1表示cv运算式对应的表达式(字符串)
用str2表示card3对应的表达式(字符串)
cv = calc(card1, card2)[rev_dic[exp[i][0]]]
str1 = '('+str(card1) + exp[i][0] + str(card2)+')'
str2 = str(card3)
然后将cv和card3进行比较以确定是否交换
if cv < card3:str1, str2 = str2, str1
3) 此处其实不需要交换,原因如下:
令左侧calc(calc(card1, card2), card3) = vc
如果vc和card4进行的是加法或乘法运算,那么交换没有意义,因为加法和乘法这两个双目运算符没有方向性,不交换既不会影响式子的正确性也不会造成重复;
如果vc和card4进行的是减法或除法运算,必然是vc-card4或vc/card4,而不可能是card4-vc或card4/vc。
因为card4是小于10的,而根据calc函数,vc一定是正数,那card4-vc必然小于10,舍;
因为card4是小于10的,而根据calc函数,vc一定大于1,那card4/vc必然小于10,舍。
所以我们不妨在写代码的时候就直接将vc放在calc函数参数表的第一个位置上!
完成目标③:
构建表达式
output.append('('+str1+exp[i][1]+str2+')'+exp[i][2]+str(card4)+'=24')
好,剩下将代码整合即可,结果如下:
def method_2(card1, card2, card3, card4):if card1 < card2:card1, card2 = card2, card1exist = check(arrange(card1, card2, card3, card4))if exist:exp = expression(exist)for i in range(len(exp)):str1 = '('+str(card1) + exp[i][0] + str(card2)+')'str2 = str(card3)rev_dic = {'+': 0, '-': 1, '*': 2, '/': 3}cv = calc(card1, card2)[rev_dic[exp[i][0]]]if cv < card3:str1, str2 = str2, str1output.append('('+str1+exp[i][1]+str2+')'+exp[i][2]+str(card4)+'=24')
9.调用组合运算函数method_1和排列运算函数method_2
以上代码均为对一定顺序的4个数字的处理
也就是说我们输入数字的顺序至关重要
而4个不同的数字一共有几种输入的顺序呢?
组合运算的种类数:
排列运算的种类数:
所以一共只有15种!
此处,我再次运用了枚举:
#组合
method_1(card[0], card[1], card[2], card[3])
method_1(card[0], card[2], card[1], card[3])
method_1(card[0], card[3], card[1], card[2])
#排列
method_2(card[0], card[1], card[2], card[3])
method_2(card[0], card[1], card[3], card[2])
method_2(card[0], card[2], card[1], card[3])
method_2(card[0], card[2], card[3], card[1])
method_2(card[0], card[3], card[1], card[2])
method_2(card[0], card[3], card[2], card[1])
method_2(card[1], card[2], card[0], card[3])
method_2(card[1], card[2], card[3], card[0])
method_2(card[1], card[3], card[0], card[2])
method_2(card[1], card[3], card[2], card[0])
method_2(card[2], card[3], card[0], card[1])
method_2(card[2], card[3], card[1], card[0])
不过,这里有一点缺陷:
重复的牌会造成重复的答案
不过既然Python总有一些神奇的库,我不妨直接用set(list)函数对output列表进行去重,省去了不少麻烦
10.输出结果
if output:answer = set(output)print('\n\n有如下解:\n')seq = 0for i in answer:seq += 1print('第'+str(seq)+'个解: ', i)print('\n一共有'+str(seq)+'个解!')
else:print('\n无解!')
大功告成!
整体代码如下:
# 24点
card = [int(input("请输入数字(1~10):")) for _ in range(4)]
print('\n四张牌如下:')
for i in card:print(str(i), end='\t')
output = [] # 盛装答案def calc(left, right): # 列举所有运算的结果if left == -1 or right == -1:ret = -1else:if left < right:left, right = right, leftadd = left + rightminus = left - righttimes = left * rightif right == 0:division = -1else:division = left / rightret = [add, minus, times, division]return retdef combine(one, two): # 将两个calc结果进行calc运算,输出所有可能(遇到-1不运算)result = []for i in range(4):for j in range(4):result.append(calc(one[i], two[j]))return result # result是16行4列的二维数组def check(result): # 输入一个combine结果(二维数组),检验是否有24;若有,返回其在二维数组中的位置position = []for i in range(16):if result[i] == -1:continueelse:for j in range(4):if result[i][j] == 24:position.append([i, j])return position # 返回24的位置(i行j列),以便回推运算式def expression(position): # 通过位置信息返回运算符sym = ['+', '-', '*', '/']exp = []for i in range(len(position)):symbol_one = position[i][0] // 4symbol_two = position[i][0] % 4symbol_three = position[i][1]exp.append([sym[symbol_one], sym[symbol_two], sym[symbol_three]])return expdef method_1(card1, card2, card3, card4): # 输出方法一——组合的结果(算式)if card1 < card2:card1, card2 = card2, card1if card3 < card4:card3, card4 = card4, card3exist = check(combine(calc(card1, card2), calc(card3, card4)))if exist:exp = expression(exist)for i in range(len(exp)):output.append('('+str(card1)+exp[i][0]+str(card2)+')'+exp[i][2]+'('+str(card3)+exp[i][1]+str(card4)+')=24')def arrange(card1, card2, card3, card4): # 类似combine函数result = []for i in range(4):for j in range(4):res1 = calc(card1, card2) # 一维数组res2 = calc(res1[i], card3) # 二维数组(4*4)res3 = calc(res2[j], card4) # 二维数组(16*4)result.append(res3)return result # result是16行4列的二维数组def method_2(card1, card2, card3, card4):if card1 < card2:card1, card2 = card2, card1exist = check(arrange(card1, card2, card3, card4))if exist:exp = expression(exist)for i in range(len(exp)):str1 = '('+str(card1) + exp[i][0] + str(card2)+')'str2 = str(card3)rev_dic = {'+': 0, '-': 1, '*': 2, '/': 3}cv = calc(card1, card2)[rev_dic[exp[i][0]]]if cv < card3:str1, str2 = str2, str1output.append('('+str1+exp[i][1]+str2+')'+exp[i][2]+str(card4)+'=24')# 检验“组合”方式
method_1(card[0], card[1], card[2], card[3])
method_1(card[0], card[2], card[1], card[3])
method_1(card[0], card[3], card[1], card[2])
# 检验“排列”方式
method_2(card[0], card[1], card[2], card[3])
method_2(card[0], card[1], card[3], card[2])
method_2(card[0], card[2], card[1], card[3])
method_2(card[0], card[2], card[3], card[1])
method_2(card[0], card[3], card[1], card[2])
method_2(card[0], card[3], card[2], card[1])
method_2(card[1], card[2], card[0], card[3])
method_2(card[1], card[2], card[3], card[0])
method_2(card[1], card[3], card[0], card[2])
method_2(card[1], card[3], card[2], card[0])
method_2(card[2], card[3], card[0], card[1])
method_2(card[2], card[3], card[1], card[0])
if output:answer = set(output)print('\n\n有如下解:\n')seq = 0for i in answer:seq += 1print('第'+str(seq)+'个解: ', i)print('\n一共有'+str(seq)+'个解!')
else:print('\n无解!')
效果图 
完结撒花!
本人是小白中的小白,写的这个程序在算法方面是异常的繁冗。
希望能给我一些鼓励和指导!感谢大家的耐心观看!谢谢大家!
这篇关于当一个准大一心血来潮,开始写24点……的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!