当一个准大一心血来潮,开始写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 + right
    minus1 = left - right
    minus2 = right - left
    times = left * right
    division1 = left / right
    division2 = right / left
    return [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 = -1
    else:
        if left < right:
            left, right = right, left
        add = left + right
        minus = left - right
        times = left * right
        if right == 0:
            division = -1
        else:
            division = left / right
        ret = [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:
            continue
        else:
            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] // 4
        symbol_two = position[i][0] % 4
        symbol_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, card1
    if card3 < card4:
        card3, card4 = card4, card3
    exist = 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, card1
    exist = 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, str1
            output.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 = 0
    for i in answer:
        seq += 1
        print('第'+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 = -1
    else:
        if left < right:
            left, right = right, left
        add = left + right
        minus = left - right
        times = left * right
        if right == 0:
            division = -1
        else:
            division = left / right
        ret = [add, minus, times, division]
    return ret


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列的二维数组


def check(result):  # 输入一个combine结果(二维数组),检验是否有24;若有,返回其在二维数组中的位置
    position = []
    for i in range(16):
        if result[i] == -1:
            continue
        else:
            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] // 4
        symbol_two = position[i][0] % 4
        symbol_three = position[i][1]
        exp.append([sym[symbol_one], sym[symbol_two], sym[symbol_three]])
    return exp


def method_1(card1, card2, card3, card4):  # 输出方法一——组合的结果(算式)
    if card1 < card2:
        card1, card2 = card2, card1
    if card3 < card4:
        card3, card4 = card4, card3
    exist = 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, card1
    exist = 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, str1
            output.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 = 0
    for i in answer:
        seq += 1
        print('第'+str(seq)+'个解: ', i)
    print('\n一共有'+str(seq)+'个解!')
else:
    print('\n无解!')

效果图 


完结撒花!

本人是小白中的小白,写的这个程序在算法方面是异常的繁冗。

希望能给我一些鼓励和指导!感谢大家的耐心观看!谢谢大家!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值