Python——Tchisla求解器(暴力搜索法)

Tchisla简介

最近玩到一个挺有意思的数字解密小游戏《Tchisla》,其规则类似算24点,也是利用一些数学运算和初始数字计算出目标数字,与算24点不同的是,Tchisla允许不限次数地使用一种初始数字(1~9),运算操作除了加减乘除外还包括了幂、平方根和阶乘,以及重复这个数字形成多位数(比如初始数字为7,那么777也可以使用)。游戏中的题目以“target # seed”的形式给出,我们的目标就是使用尽量少的初始数字seed加以各种计算得到目标数字target,比如题目11 # 7,最简解法就是11 = 77 / 7,比如题目5 # 9,最简解法为5 = √9 + (√9)! / √9。这里有篇文章更详细地介绍了这个游戏插电数字游戏——Tchisla 。
在这里插入图片描述
在这里插入图片描述

Tchisla求解器

这个游戏讲道理还是挺难的,尤其是挑战题目中的目标数都上千了,手动解题很难找到最优解,因此我花了半天用python写了个求解器,目前看起来效果不错,几十秒钟就帮我找到了了2016 # 1~9的所有最优解:
在这里插入图片描述

在求解Tchisla题目的过程中我相信是有一些内在的数学规律的,不过似乎很难总结出一套普适的最优求解算法,还是得暴力求解。暴力求解的思路其实挺简单的,从1个初始数字开始,找到所有合理的可达值及其表达式,放入一个列表中,这个称为第1代。后面再继续搜索第2代、第3代等等,对于第n代,其中的可达值就是n个初始数字可以表示的所有结果,计算第n代时我们可以根据前面的n-1代结果进行组合来生成新一代数据,比如5个初始数字可以得到的目标值就是第1代结果+第4代结果的各种算数组合加上第2代结果+第3代结果的各种算数组合。过程中一但发现目标值,我们就得到答案了。

虽然思路很简单,不过其中有些要注意的点,首先开方和阶乘运算这样的单目运算符不消耗初始数字,可以对一个表达式无限操作,为了避免无限搜索下去,所以第一点限制就是开方只对整数做、阶乘只对小于一定值的整数做。另外浮点数的精度丢失问题比较麻烦,需要合适的取整。此外,为了避免一些不太可能的无效搜索,合理的限制搜索数据的最大值也是必要的。

除了以上思路之外,为了得到结果,我们还需要一套表达式系统来跟踪我们的计算步骤,毕竟我们需要知道的最终是如何运算得到目标值而不仅仅是可以得到目标值的一个肯定。

以下放出代码,除了阶乘调用了系统库外,没有依赖别的库。代码前半部分是表达式系统,后半部分是求解器:

from math import factorialclass Expr:threshold = 1e-10def __init__(self, value):if isinstance(value, float):if value.is_integer():value = int(value)elif abs(value - round(value)) < Expr.threshold:value = round(value)self.value = valuedef __str__(self):raise TypeError("Should not call Expr.__str__()")class LiteralExpr(Expr):def __init__(self, value):Expr.__init__(self, value)self.literal = str(value)def __str__(self):return self.literalclass FactorialExpr(Expr):def __init__(self, expr: Expr):Expr.__init__(self, factorial(expr.value))self.child = exprdef __str__(self):if isinstance(self.child, BinaryExpr):return '({})!'.format(str(self.child))else:return str(self.child) + '!'class SqrtExpr(Expr):def __init__(self, expr: Expr):Expr.__init__(self, expr.value ** 0.5)self.child = exprdef __str__(self):if isinstance(self.child, LiteralExpr):return '√{}'.format(str(self.child))else:return '√({})'.format(str(self.child))class BinaryExpr(Expr):def __init__(self, oper: str, left: Expr, right: Expr, value):Expr.__init__(self, value)self.oper = operself.left = leftself.right = rightdef __str__(self):format_str = '({})' if isinstance(self.left, BinaryExpr) else '{}'format_str += ' {} 'format_str += '({})' if isinstance(self.right, BinaryExpr) else '{}'return format_str.format(str(self.left), self.oper, str(self.right))class AddExpr(BinaryExpr):def __init__(self, left: Expr, right: Expr):BinaryExpr.__init__(self, '+', left, right, left.value + right.value)class SubExpr(BinaryExpr):def __init__(self, left: Expr, right: Expr):BinaryExpr.__init__(self, '-', left, right, left.value - right.value)class MulExpr(BinaryExpr):def __init__(self, left: Expr, right: Expr):BinaryExpr.__init__(self, '*', left, right, left.value * right.value)class DivExpr(BinaryExpr):def __init__(self, left: Expr, right: Expr):BinaryExpr.__init__(self, '/', left, right, left.value / right.value)class PowExpr(BinaryExpr):def __init__(self, left: Expr, right: Expr):BinaryExpr.__init__(self, '^', left, right, left.value ** right.value)class TchislaSolver:value_limit = 10 ** 8power_limit = 30factorial_limit = 15class Found(BaseException):passdef __init__(self, target: int, seed: int):self.target = targetself.seed = seedself.all_candidates = set()self.current_candidates = []self.generations = []self.result = Nonedef solve(self, search_depth=10):try:while search_depth > 0:begin, end = 0, len(self.generations) - 1while begin <= end:self.cross_candidates(self.generations[begin], self.generations[end])begin += 1end -= 1self.add_literal(len(self.generations) + 1)self.next_generation()search_depth -= 1except TchislaSolver.Found:passreturn self.resultdef cross_candidates(self, c1, c2):for expr1 in c1:for expr2 in c2:self.add_addition(expr1, expr2)self.add_subtraction(expr1, expr2)self.add_multiplication(expr1, expr2)self.add_division(expr1, expr2)self.add_power(expr1, expr2)def add_candidate(self, expr):if expr.value == self.target:self.result = exprraise TchislaSolver.Found()if expr.value > TchislaSolver.value_limit:returnif expr.value not in self.all_candidates:self.all_candidates.add(expr.value)self.current_candidates.append(expr)self.add_factorial(expr)self.add_square_root(expr)def next_generation(self):self.generations.append(self.current_candidates)self.current_candidates = []def add_literal(self, repeats: int):self.add_candidate(LiteralExpr(int(str(self.seed) * repeats)))def add_addition(self, expr1: Expr, expr2: Expr):self.add_candidate(AddExpr(expr1, expr2))def add_subtraction(self, expr1: Expr, expr2: Expr):if expr1.value > expr2.value:self.add_candidate(SubExpr(expr1, expr2))else:self.add_candidate(SubExpr(expr2, expr1))def add_multiplication(self, expr1: Expr, expr2: Expr):self.add_candidate(MulExpr(expr1, expr2))def add_division(self, expr1: Expr, expr2: Expr):if expr1.value == 0 or expr2.value == 0:returnself.add_candidate(DivExpr(expr1, expr2))self.add_candidate(DivExpr(expr2, expr1))def add_power(self, expr1: Expr, expr2: Expr):if isinstance(expr2.value, int) and expr2.value <= TchislaSolver.power_limit:self.add_candidate(PowExpr(expr1, expr2))if isinstance(expr1.value, int) and expr1.value <= TchislaSolver.power_limit:self.add_candidate(PowExpr(expr2, expr1))def add_factorial(self, expr: Expr):if isinstance(expr.value, int) and expr.value <= TchislaSolver.factorial_limit:self.add_candidate(FactorialExpr(expr))def add_square_root(self, expr: Expr):if isinstance(expr.value, int) and expr.value > 0:self.add_candidate(SqrtExpr(expr))

测试

这个求解器用起来很简单,可以编写一个简单的例子进行测试,计算2016 # 1~9的最优解:

for i in range(1, 10):ts = TchislaSolver(2016, i)print('2016 = ' + str(ts.solve()))

结果如下:
在这里插入图片描述
代码中对搜索和运算是有一些限制的,默认搜索10代,可达值最大只接受10^8,幂指数最大30,阶乘数最大15,这些限制可以加快搜索速度,一般是够用的,如果没搜索到最优解,也可以尝试修改以下值来调整并重新搜索:

  value_limit = 10 ** 8power_limit = 30factorial_limit = 15

从上面的结果图里还可以发现表达式系统还有优化的空间,有些括号是多余的,不过懒得搞了,能用就行~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/496528.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Qt 学习之路】使用 cmake 在Windows上 编译 ZeroMQ

文章目录 1、概述2、编译2.1、用 Visual Studio 的解决方案方式2.1.1、找到 Builds 文件夹2.1.2、查看 deprecated-msvc 下的 libzmq.sln 文件2.1.3、使用 Visual Studio 打开 libzmq.sln 解决方案2.1.4、修改 libzmq.import.props 文件2.1.5、编译生成 2.2、用 C 的cmake方式2…

C语言中如何进行内存管理

主页&#xff1a;17_Kevin-CSDN博客 收录专栏&#xff1a;《C语言》 C语言是一种强大而灵活的编程语言&#xff0c;但与其他高级语言不同&#xff0c;它要求程序员自己负责内存的管理。正确的内存管理对于程序的性能和稳定性至关重要。 一、引言 C 语言是一门广泛使用的编程语…

Twing模板注入 [BJDCTF2020]Cookie is so stable1

打开题目 我们先抓包分析一下 可以输入{{7*7}}处发包试一下 可以看到在cookie处存在ssti模板注入 输入{{7*‘7’}}&#xff0c;返回49表示是 Twig 模块 输入{{7*‘7’}}&#xff0c;返回7777777表示是 Jinja2 模块 在这里可以看出是Twing模块 我们直接用固定payload就可以…

搬家了,发现虚拟机链接不上,查找原因,解决了

是网络配置的问题&#xff0c;因为ip地址变动&#xff0c;所以配置文件要进行改动 1.通过cmd查看本地主机ip地址 2.在虚拟网络编辑器中选在vmnet8&#xff0c;用管理员权限修改ip&#xff0c;网关&#xff0c;子网掩码&#xff0c;和物理主机对应 3.在/etc/sysconfig/network…

JavaEE:多线程(3):案例代码

多线程基础知识要点 案例一&#xff1a;单例模式 是一种设计模式 软件设计需要框架&#xff0c;这是硬性的规定&#xff1b;设计模式是软性的规定。遵循好设计模式&#xff0c;代码的下限就被兜住了 单例 单个实例&#xff08;对象&#xff09; 某个类在一个进程中只应该创…

GIS之深度学习02:Anaconda2019版本安装(py38)

Anaconda是一个专注于数据科学和机器学习的开源发行版&#xff0c;内置了丰富的工具和库&#xff0c;包括Python解释器、NumPy、SciPy、Pandas、Scikit-learn、TensorFlow等&#xff0c;使用户能够轻松进行科学计算和数据分析。其强大的包管理器conda简化了软件包的安装和环境管…

redis-Redis主从,哨兵和集群模式

一&#xff0c;Redis的主从复制 ​ 主机数据更新后根据配置和策略&#xff0c; 自动同步到备机的master/slaver机制&#xff0c;Master以写为主&#xff0c;Slave以读为主。这样做的好处是读写分离&#xff0c;性能扩展&#xff0c;容灾快速恢复。 1.1 环境搭建 如果你的redi…

图论-算法题

797. 所有可能的路径 题目: 给你一个有 n 个节点的 有向无环图&#xff08;DAG&#xff09;&#xff0c;请你找出所有从节点 0 到节点 n-1 的路径并输出&#xff08;不要求按特定顺序&#xff09; graph[i] 是一个从节点 i 可以访问的所有节点的列表&#xff08;即从节点 i …

Java面向对象之接口和抽象类的区别一目了然

介绍 相信对于Java面向对象部分&#xff0c;很多人很长一段时间对于接口和抽象类的区别&#xff0c;使用场景都不是很熟悉&#xff0c;同是作为抽象层重要的对象&#xff0c;工作中到底什么情况下使用抽象类&#xff0c;不是很清楚。本文就一次性把这些概念一次性说清楚&#x…

K线实战分析系列之八:十字星——容易识别的特殊形态

K线实战分析系列之八&#xff1a;十字星——容易识别的特殊形态 一、十字启明星和十字黄昏星二、弃婴底部形态和弃婴顶部形态三、总结十字启明星和十字黄昏星形态的要点 一、十字启明星和十字黄昏星 当开盘价与收盘价极为接近的时候&#xff0c;当期的K线就呈现为一根十字线&am…

微服务Springcloud智慧工地APP源码 AI人工智能识别 支持多工地使用

目录 一、现状描述 二、行业难点 APP端功能 一、项目人员 二、视频监控 三、危大工程 四、绿色施工 五、安全隐患 AI智能识别 环境监测 实名制管理 智慧监测 智慧工地全套解决方案 一、现状描述 建筑工程建设具有明显的生产规模大宗性与生产场所固定性的特点。建…

【C++练级之路】【Lv.9】【STL】stack类和queue类的模拟实现

快乐的流畅&#xff1a;个人主页 个人专栏&#xff1a;《C语言》《数据结构世界》《进击的C》 远方有一堆篝火&#xff0c;在为久候之人燃烧&#xff01; 文章目录 一、容器适配器二、stack2.1 push2.2 pop2.3 top2.4 size2.5 empty 三、queue3.1 push3.2 pop3.3 front3.4 back…