当一个准大一心血来潮,开始写24点……

2024-01-15 03:10
文章标签 24 心血来潮 准大一

本文主要是介绍当一个准大一心血来潮,开始写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个不同的数字一共有几种输入的顺序呢?

组合运算的种类数:

Nc=C_{4}^{2}\div 2=3

排列运算的种类数:

Na=C_{4}^{2}\times 2=12

所以一共只有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点……的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/607461

相关文章

Science|癌症中三级淋巴结构的免疫调节作用与治疗潜力|顶刊精析·24-09-08

小罗碎碎念 Science文献精析 今天精析的这一篇综述,于2022-01-07发表于Science,主要讨论了癌症中的三级淋巴结构(Tertiary Lymphoid Structures, TLS)及其在肿瘤免疫反应中的作用。 作者类型作者姓名单位名称(中文)通讯作者介绍第一作者Ton N. Schumacher荷兰癌症研究所通讯作者之一通讯作者Daniela S. Thomm

SIGMOD-24概览Part7: Industry Session (Graph Data Management)

👇BG3: A Cost Effective and I/O Efficient Graph Database in ByteDance 🏛机构:字节 ➡️领域: Information systems → Data management systemsStorage management 📚摘要:介绍了字节新提出的ByteGraph 3.0(BG3)模型,用来处理大规模图结构数据 背景

【A题成品论文已出】24数学建模国赛A题成品论文(附参考代码)免费分享

A 题  “板凳龙”  闹元宵 摘要 “板凳龙”是一种传统的民俗文化活动,通常由许多板凳连接成龙的形状进行表演。本文基于螺旋线和板凳龙的运动特性,建立数学模型来分析舞龙队在不同情况下的运动轨迹、调头路径和速度优化等问题。问题主要涉及板凳龙的行进路径、碰撞避免、调头空间的设计,以及如何优化龙头的速度,以确保龙身与龙尾的行进安全。 针对问题一,舞龙队由223节板凳组成,龙头前把手的速度为1

【Git 学习笔记_24】Git 使用冷门操作技巧(四)——更多实用 git 别名设置、交互式新增提交

文章目录 11.8 更多别名设置别名1:只查看当前分支(git b)别名2:以图表形式显示自定义格式的 git 日志(git graph)别名3:查看由于合并分支导致的冲突后仍有冲突的、待合并的文件列表(git unmerged)别名4:查看 git 状态(git st)别名5:查看 git 简要状态(git s)别名6:查看最新版本的统计信息(git l1)别名7:查看最近 5 个版本的提

Leetcode面试经典题-24.两两交换链表中的节点

解法都在代码里,不懂就留言或者私信 这里先写一个递归的解,如果后面有时间,我再写个迭代的 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val =

图形API学习工程(24):D3D11读取非DDS格式的CubeMap

工程GIT地址:https://gitee.com/yaksue/yaksue-graphics 目标 在《图形API学习工程(21):使用CubeMap纹理》中,由于DirectX读取CubeMap的教程范例都是DDS格式的纹理,因此我也首先实现了DDS的版本,期望之后做处理。 上一篇使D3D12可以用非DDS格式的CubeMap了,本篇目标将是D3D11。 分析当前的流程 当前使用D

数字人直播防封技巧升级!头部源码厂商如何实现7*24小时无间断直播?

当前,许多用户在使用数字人直播的过程中都遇到了直播间违规和账号被封两大问题,并因此蒙受了一定的损失。在此背景下,不少有计划引入数字人直播的企业和搭建数字人直播系统的创业者也开始有了犹豫。为了让大家能够更放心地入局,本期,我们将通过分析这两大问题出现的原因,来整理数字人直播防封教程,希望能对大家有所帮助。 一、数字人直播是否会导致直播间违规和封号问题? 需要明确的一点是,当前,虽然许多人在进

【24数模国赛赛题思路已出】国赛B题第二套整体思路丨附参考代码丨免费分享

B 题 生产过程中的决策问题 一、问题1解析 问题1的任务是为企业设计一个合理的抽样检测方案,基于少量样本推断整批零配件的次品率,帮助企业决定是否接收供应商提供的这批零配件。具体来说,企业需要依据两个不同置信度(95% 和 90%)来判断次品率是否超过或不超过标称值(10%)。 供应商声称的次品率为不超过10%,但企业需要通过抽样检测来验证实际次品率是否符合此要求。通过设计合理的抽样方案,企

org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 24 in XML document from

org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 24 in XML document from class path resource [bean1.xml] is invalid; nested exception is org.xml.sax.SAXParseException; lineN

24. Redis缓存问题

1. 前言 在小型项目中(例如大部分 toB 业务),Redis 被作为缓存,我们无需过多关注缓存的性能,但是对于高并发的场景(例如 toC 的在线电商业务),在商品秒杀或者库存抢购的时候,Redis 也可能存在诸多潜在的问题,例如缓存穿透、缓存雪崩。 2. 缓存问题 2.1 缓存穿透 面试官提问: Redis 的缓存穿透是什么意思?有什么解决方案? 题目解析: 首先给出缓