python算24点穷举法_关于24点去重的算法?

=== 4月12日更新 ===

=== 先给结论吧 ===

花了近一周时间用JavaScript完成了24点去重算法,源码提交到了github上:auntyellow/24 ,可以在线试:gives you all dissimilar solutions.

在1到13范围内的四数组合中,不重复解最多的组合是2、4、8、10:

10 + 8 + 4 + 2 = 24

(10 - 4) × 8 ÷ 2 = 24

(10 × 4 + 8) ÷ 2 = 24

((10 + 2) × 8 ÷ 4 = 24

10 × 2 + 8 - 4 = 24

(10 - 2) × 4 - 8 = 24

8 × 4 - 10 + 2 = 24

(8 ÷ 4 + 10) × 2 = 24

(8 × 2 - 10) × 4 = 24

(10 - 8 ÷ 2)) × 4 = 24

10 × 4 - 8 × 2 = 24

只能用分数来解的(16个,这里不给答案了,有兴趣可以自己练练):

1, 3, 4, 6

1, 4, 5, 6 (这题居然有两解,都必须用分数的)

1, 5, 5, 5

1, 6, 6, 8

1, 8, 12, 12

2, 2, 11, 11

2, 2, 13, 13

2, 3, 5, 12

2, 4, 10, 10

2, 5, 5, 10

2, 7, 7, 10

3, 3, 7, 7

3, 3, 8, 8

4, 4, 7, 7

5, 5, 7, 11

5, 7, 7, 11

其他有难度的,就是中间过程必须有大数的(大于36就很难一下子想到了)(像a × b - a × c = 24这种形式,比如10、12、12、12,其实并没有太大难度,就没有列进去):

1, 7, 13, 13

6, 12, 12, 13

1, 6, 11, 13

6, 11, 12, 12

5, 10, 10, 13

1, 5, 11, 11

5, 10, 10, 11

4, 8, 8, 13

4, 4, 10, 10

4, 8, 8, 11

6, 9, 9, 10

3, 8, 8, 10

3, 5, 7, 13

3, 6, 6, 11

1, 2, 7, 7

5, 8, 9, 13

5, 9, 10, 11

4, 7, 11, 13

4, 9, 11, 11

4, 10, 10, 11

6, 7, 7, 11

3, 5, 8, 13

5, 5, 8, 11

2, 3, 13, 13

还找到一个难的:3、7、9、13,它有两种解法,一种用到了分数,一种有大数。

为了验证这些结论,还是查到了 @常成 那边,包括 理论 - 24理论 解决二十四点 (我的算法跟这里相当接近了)、所有独立解 - 24理论 解决二十四点 (解法最多的牌型确实有11个解),需要分数的解 - 24理论 解决二十四点 (确实有16个牌型),看来程序是没太大问题了。

=== 然后说说算法 ===

参考了本题 小于0 的回答,还有 24点算法,如何给出所有不同的答案 - 萝卜的回答 - SegmentFault ,总之就是列出所有不等价表达式,例如 (( a + b ) * c) / d 和 (( b + a) * c ) / d 是等价的,需要去重。

虽然是重复在做很多人以前做过的工作,但还是有些自认为别出心裁的思路,因为并没从代数形式上做分析,而是通过试数的办法做的,试的是π、e、lnπ和arctan e这四个超越数,对近似值做比较(浮点数运算总是有误差的)来判断两个表达式是否等价。(我把近似度设定在1e-6其实算是碰巧蒙对了,SegmentFault的萝卜指出lnπ/(e + π/arctan(e))和π/e - lnπ/arctan(e)只相差7.9e-6,如果把近似度再提高1个数量级,结果可能就不对了。)

5种括号型(((oxo)xo)xo、(ox(oxo))xo、(oxo)x(oxo)、ox((oxo)xo)、ox(ox(oxo)),其中o代表数字,x代表运算符),4个数一共有24种排列,3个符号一共有64种排列,总共需要“试数”的表达式总共有7680个,在这些表达式中找出了1170种不等价的,也和网上能找到的资料相吻合,例如 小于0 给我推荐的 A140606 - OEIS 。

后来发现,仅仅用这1170个表达式是不够的,还要考虑以下14种牌型:

a, a, b, c // 两个相同的数可以交换,也可以抵消

a, a, b, b

a, a, a, b

a, a, a, a

1, a, b, c // 1可以舍去

1, a, a, b

1, a, a, a

1, 1, a, b

1, 1, a, a

1, 1, 1, a

2, 2, a, b // 2 + 2 = 2 × 2,这个算重复解应该说得过去

2, 2, a, a

1, 2, 2, a

2, a, a, b // 2 × a - a = (a + a) ÷ 2,这个居然被我算成重复解了!

另外还有,a、a'(=a+1)、b、c这种牌型,需要把(a'-a)参与乘除运算的解法排除掉,然后单独算b+c、b*c有没有可能等于24。

所以程序里绝大部分逻辑都是在判断:牌型到底属于上面列出来的15种当中的哪一种,写得相当啰嗦。

另外还有一些小问题,比如:1、1、5、5,只给出了一种解,因为对牌型1、1、a、a组成的表达式来说, (a+1)(a-1)和a*a-1*1是等价的;

没有考虑4/2和4-2等价的问题,例如2、4、6、6,(6-(4-2))*6和(6-4/2)*6被认为是两个不等价的解(凭什么2+2和2*2等价,但4-2和4/2不等价?)

当2作为中间步骤时,没考虑2+2和2*2的等价,还拿2、4、6、6说事,(6-4+2)*6和(6-4)*2*6是不等价的解(写到这里我真后悔把2+2和2*2算做等价了)

仔细想想,还真不能轻易认为2+2=2*2、4-2=4/2是等价解法,要是真这么算的话,那么我们可以写出:

(6-4/2)*6 = (6-(4-2))*6 = (6-4+2)*6 = (6-4)*2*6

显然每个等号左右两边都是等价的。但要说最左边的和最右边的是重复的解法,那又说不过去了。

看似很简单的问题,本以为可以花半天时间搞定的,结果编码、测试、验证、优化一系列过程居然花了1周的时间,再次印证了我的盲目乐观 :-(

=== 更早的回答 ===

我在SegmentFault上提了一个相似的问题,问完才发现知乎上已经有了。很快就有人给出漂亮的解答了:24点算法,如何给出所有不同的答案 - 萝卜的回答 - SegmentFault ,起初答题者思路跟 小于0 的回答类似,后来发现穷举太麻烦,就改用符号代数,在Mathimatica里用10余行代码搞定了,真让我吃惊。

另外,对于重复解的定义,还是有挺大争论的,比如我认为2x2和2+2应该算是雷同的,但很多人并不认同。

转载一下:

Clear[game24]game24[input_List,result_:24]:=Block[{add,sub,mul,div},With[{oprules={add->Plus,sub->Subtract,mul->Times,div->Divide},specifics={div[x_,1]:>x,mul[x_,1]:>x,mul[1,x_]:>x,add[2,2]->mul[2,2]}},Map[RightComposition[Hold,ReplaceAll[oprules],ToString[#,InputForm]&,StringDelete[{"Hold[","]"}],StringReplace[{"*"->"\[Times]","/"->"\[Divide]"}]],Union[Select[result==(#/.oprules)&]@Groupings[Permutations@input,{add,sub,mul,div}->2],SameTest->(0===Simplify[sub[#1,#2]//.specifics/.Prepend[oprules,k_Integer:>ToString[k]]]&)]]]]用符号add、sub、mul、div分别对应加减乘除四则运算,构建二叉树代表算式。Groupings函数生成了所有可能的表达式二叉树。

Select筛选出计算结果符合要求的。

Union负责除去雷同的算式。它的SameTest选项计算两个代数式的差化简后是否为0。注意这里通过把数字转为字符进行“符号化”了,而且对数字1、2进行了特殊处理(specifics)。

最后Map负责把每个算式转成字符串输出。

测试:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值