Python计算24点:递归程序设计艺术(9)

给定4个1~13之间的数,用任意多的加减乘除和括号计算这4个数,使得最终结果是24,这就是著名的24点问题。本节课的目的是编写一个程序,能够用给定的4个数凑出24。也就是说,让电脑而不是人脑计算24点。这一题看起来难度很大,但是只要坚持递归三步曲问题一样可以解决。

首先把原问题分解为两个子问题:1) 4个数能凑出哪些表达式,以及每个表达式的值;2) 上述表达式中哪些的值是24。第二个问题好解决。第一个问题可以用一个递归程序解决,首先明确第一个问题的参数是nums,表示当前可以使用的数的列表。注意这里只能使用列表或者numpy.ndarray类型而不能使用集合,因为4个数中可能有重复。

明确参数后,可以知道当len(nums)=1时是递归边界。此时列表中仅有一个数,不能构成表达式,所以结果就是这个数本身。递归假设:只要nums中的数至少少一个,则函数能计算出所有可能的表达式及其对应的值。

递归推导是这样的,先把nums中的数分为任意的两组,比如假设nums中有3个数,则这3个数可以分为两组,每组中的数分别有1个和2个或者2个和1个。然后根据每组数量n,从nums中取n个数的组合,这样就确定了每组中具体有哪些数。接着进行递归调用,求出每组所能凑出的所有表达式及其值。最后让这两组表达式两两组合,用加减乘除连接成新的表达式,并算出结果即可。代码如下:

求24点的Python程序

import itertools
import numpy as np

def make_num(nums, target):
    # 用nums列表中的数,凑出target
    print(nums, target)
    result = get_exp_value_pairs(nums)
    for exp in result:
        if result[exp] == target:  # 如果表达式的值等于目标
            print(exp)
    print('完成')

def get_exp_value_pairs(nums):
    # 返回由nums中的数据所能构成的表达式及其值
    if len(nums) == 1:
        return {str(nums[0]): nums[0]}  # 表达式和值都是这个数本身
    result = {}
    for left in range(1, len(nums)): # 左表达式用到left个数
        # 从nums序列中,取left个数的组合
        for l_nums in itertools.combinations(nums, left):
            r_nums = nums.copy()
            for e in l_nums:
                r_nums.remove(e)
            l_result = get_exp_value_pairs(list(l_nums))  # 递归调用
            r_result = get_exp_value_pairs(r_nums)  # 递归调用
            for l_exp in l_result:  # 对每个左表达式循环
                l_val = l_result[l_exp] # 左表达式对应的值
                for r_exp in r_result:  # 对每个右表达式循环
                    r_val = r_result[r_exp]  # 右表达式的值
                    result['(%s + %s)' % (l_exp, r_exp)] = l_val + r_val
                    result['(%s - %s)' % (l_exp, r_exp)] = l_val - r_val
                    result['(%s - %s)' % (r_exp, l_exp)] = r_val - l_val
                    result['%s * %s' % (l_exp, r_exp)] = l_val * r_val
                    if r_val != 0:
                        result['%s / %s' % (l_exp, r_exp)] = l_val / r_val
                    if l_val != 0:
                        result['%s / %s' % (r_exp, l_exp)] = r_val / l_val
    return result

if __name__ == '__main__':
    # 生成1~13之间的4个随机数
    numbers = np.random.randint(1, 14, [4])
    make_num(list(numbers), 24)
    make_num([3, 3, 7, 7], 24)

在get_exp_value_pairs()的第4行,变量result用来保存最终结果,它的类型是字典而不是列表,这是因为结果中会存在重复的表达式,使用字典可以避免这种重复。同一个函数的第2个for循环中使用了itertools内部包的combinations(a, r)函数,该函数返回从序列a中取r个数的组合。比如combinations([3, 7, 5], 2)的结果是一个等价于列表[(3, 7), (3, 5), (7, 5)]的序列。

倒数第3行调用了numpy.random.randint(abs)函数,a和b表示随机数的上下界,其中下界是不包含在内的。参数s表示结果的形状,[4]表示返回一个含有4个随机数的向量。代码倒数第2行把这个向量转成了列表,因为get_exp_value_pairs()要用到列表的.copy()成员函数以复制出一个新列表。代码运行结果如下:

[12, 9, 10, 4] 24
12 * 10 / (9 - 4)
12 / (9 - 4) / 10
10 * 12 / (9 - 4)
10 / (9 - 4) * 12
完成
[3, 3, 7, 7] 24
7 * (3 + 3 / 7)
7 * (3 / 7 + 3)
(3 + 3 / 7) * 7
(3 / 7 + 3) * 7
完成

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

方林博士

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值