算 24 点
算 24 点游戏是一种经典的用扑克牌来进行的益智游戏。游戏内容是:从一副扑克牌中抽去大小王剩下 52 张,任意抽取 4 张牌,把牌面上的数字(J、Q、K、A 分别代表 11、12、13、1)运用加、减、乘、除和括号进行运算得出 24。每张牌都必须使用一次,但不能重复使用。
请编写代码对任意给出的四张牌算 24 点,用文本输出解答。
A |
B |
C |
D |
|
1 |
[3,3,8,8] |
[1234,1243,1324,1342,1423,1432,2134,2143,2314,2341,2413,2431,3124,3142,3214,3241,3412,3421,4123,4132,4213,4231,4312,4321] |
[+,-,*,/] |
=C1.conj@r(C1.(C1.(~/get(1)/get(2)))) |
2 |
for B1 |
=A1(string(A2).split@p()) |
>a=B2(1),b=B2(2),c=B2(3),d=B2(4) |
|
3 |
for D1 |
=B3.split() |
>x=C3(1),y=C3(2),z=C3(3) |
|
4 |
=a/x/"("/b/y/"("/c/z/d/"))" |
|||
5 |
if round(eval(C4),4)==24 |
=@|C4 |
||
6 |
=a/x/"(("/b/y/c/")"/z/d/")" |
|||
7 |
if round(eval(C6),4)==24 |
=@|C6 |
||
8 |
="("/a/x/b/")"/y/"("/c/z/d/")" |
|||
9 |
if round(eval(C8),4)==24 |
=@|C8 |
||
10 |
="(("/a/x/b/")"/y/c/")"/z/d |
|||
11 |
if round(eval(C10),4)==24 |
=@|C10 |
||
12 |
=a/x/"(("/b/y/c/")"/z/d/")" |
|||
13 |
if round(eval(C12),4)==24 |
=@|C12 |
||
14 |
=[D5,D7,D9,D11,D13].conj().id() |
http://try.scudata.com.cn/try.jsp?splx=ExA009essd.splx
A1给出计算所用的4张牌。B1枚举出4张牌所有可能的排列顺序。C1列出可选的运算符号。
由于4张牌之间需要放置3个运算符号,每个都可能任意选择,D1中用多层循环列出所有可能的符号选择。SPL中,在最外层中用A.conj@r()可以将各层的结果合并成一个序列。D1中结果如下:
B1中的枚举结果,如果不想手动去写,也可以类似写为=4.conj@r(4.(4.(4.([~,get(1),get(2),get(3)]).select(~.icount()==4).(~.concat())))),与C1不同的是,需要选出用到了所有4张牌,即每张牌都只用1次的排列方案。
A2循环每一种牌的排列顺序,B2根据顺序,得到算24所需的4个数,为了便于使用,C2中将这四个数赋值给a,b,c,d四个变量。
B3对于每一组数,循环可使用的运算符。C3将运算符拆分为序列,D3将其分别用变量x,y,x表示。
对于给定了顺序和符号的一种组合:a#b#c#d,根据不同的运算顺序,有5种情况,分别用括号表示如下:a#(b#(c#d)),a#((b#c)#d),(a#b)#(c#d),((a#b)#c#d和(a#(b#c))#d。
在循环体中依次尝试。如C4按照a#(b#(c#d))的运算顺序,拼出表达式,C5用eval计算出表达式的计算结果。为了避免计算误差影响,需要用round函数处理结果,这里保留4位小数。如果表达式计算的结果正好是24,说明C4中的表达式满足要求,把结果记录在D5中。类似的,C6,C8,C10,C12按照另外4种运算顺序拼出表达式,并把计算结果为24的表达式分别记录在D7,D9,D11和D13中。
循环完成后,A14中取出所有满足要求的表达式,并用conj拼为一个序列。因为给出的牌有可能重复,因此结果可能存在相同的表达式,还需要用id去重。A14得到的结果如下:
上面的解法中,在对给定顺序的数和符号的组合,分析5种运算顺序时,实际上执行了类似的处理和判断,还可以用循环处理,进一步简化代码,如下:
A |
B |
C |
D |
|
1 |
[3,3,8,8] |
[1234,1243,1324,1342,1423, 1432,2134,2143,2314,2341, 2413,2431,3124,3142,3214, 3241,3412,3421,4123,4132, 4213,4231,4312,4321] |
[+,-,*,/] |
=C1.conj@r(C1.(C1.(~ /get(1)/get(2)))) |
2 |
[] |
[?/?/"("/?/?/"("/?/?/?/"))", ?/?/"(("/?/?/?/")"/?/?/")", "("/?/?/?/")"/?/"("/?/?/?/")", "(("/?/?/?/")"/?/?/")"/?/?, ?/?/"(("/?/?/?/")"/?/?/")"] |
||
3 |
for B1 |
=A1(string(A3).split@p()) |
||
4 |
for D1 |
=B4.split() |
||
5 |
>B2.(exp=eval(~,B3(1),C4(1),B3(2), C4(2),B3(3), C4(3), B3(4)), if(round(eval(exp),4)==24, A2|=exp)) |
|||
6 |
=A2.id() |
http://try.scudata.com.cn/try.jsp?splx=ExA009essd2.splx
A2准备存储结果,B2中将5种运算顺序对应的表达式列了出来,其中各个变量都用?表示。在循环中,C5对给定顺序的数与符号组合执行判断时,循环对B2中的每种情况判断,其中eval(s, x1,x2,…)可以用参数x1,x2,…的值依次替换字符串s中的?,这里按照所需顺序把数和符号间隔输入参数列表,就能得到对应的表达式exp,再次使用eval即可计算表达式的结果,如果得到24则将表达式记录在A2中。
英文版
这篇文章中的写法输出的结果好像少了几个。
跟另外一篇 趣味集算:算二十四 不一样。
跟着练习一下递归:SPL24splxzip