前段時間小舅子(小學生)過來玩,沒事一起玩3*8=24,遇到難算的半天都想不出來,所以就想有沒有app或者小工具啥的,搜了一下,有工具,但是不好用,所以就想自己寫個簡單易用的。
開始着手做的時候,發現運算邏輯無法總結成簡單的計算公式,百度也沒找到有人完整的實現,最后只能用最笨的方法了,且聽我娓娓道來。
首先,按照常規,共有四個正整數參與運算,取值區間均為 [1,10](包含1和10),僅有加減乘除以及括號運算,不存在其他高級運算(如平方、開方等等),這四個數的計算順序不確定,每個數字參與且僅參與一次運算。
按數學的思路來對這個常規描述進行解析,如下:
- 四個數 a、b、c、d ;
- 取值范圍 1<=a<=10,1<=b<=10,1<=c<=10,1<=d<=10;
- 運算方式 :+、 -、 *、 / 以及 () ;
運算順序不確定,所以我們暫時認為 a+b 、 b+a 、(a+b) 與 (b+a),a*b 、 b*a 、(a*b) 與(b*a) 等等均互為不相同的計算(至於把他們認為是相同的計算,還需要更深入的研究了,這里的愚笨算法暫時無法區分)。
我們先確定四個空位(美其名曰 A、B、C、D),在計算的時候往這四個空位里填上這四個數字,如果按照我下面描述的計算方式最終得到 24 ,那么我們就認為這種數字與計算的組合就是一種"3*8=24"的計算方法,否則,我們繼續遍歷。
我仔細思考了一下,這四個空位的結合順序一共有 5 種:
1、{[(A,B)C]D}
2、{[A(B,C)]D}
3、{A[(B,C)D]}
4、{A[B(C,D)]}
5、[(A,B),(C,D)]
有了這些結合順序之后,我們只需要把我們的四個數字分別放到這四個空位中,並且在這四個空位之間分別放上四種計算方式(+、 -、 *、 /)中的一種,然后根據這個組合計算出結果,並判斷是否等於24,如果是,那么就找到了一種,否則繼續往下執行。
所以,看到此處,大家可以思考一下,這種愚笨的算法所需要的循環層次有幾級。
絕對不會有 8 級。
也不會少於 4 級。
通過巧妙的數據結構設計,我將循環的層次控制在了4級,還好運算量不大,速度是可以接受的,不然寫一個4級循環來實現功能真是要命(被嫌棄死)。
首先,由於每個數字必須參與運算且只能參與一次運算,我必須先把這些數字的排列順序找出來。比如 1,2,3,4四個數字,他們的排列為:1234、1243、1324、1342.等等。
var minNum = 1, //最小值 maxNum = 10,//最大值 opt = ['+', '-', '*', '/'],//運算 dataStruct = function (a, b, c, d) { //構建特殊數據結構 this[1] = a; this[2] = b; this[4] = c; this[8] = d; }, getGroup = function (data) { //對dataStruct結構的數字進行排列組合 var group = []; try { for (var i1 = 1; i1 <= 8; i1 *= 2) { for (var i2 = 1; i2 <= 8; i2 *= 2) { for (var i3 = 1; i3 <= 8; i3 *= 2) { for (var i4 = 1; i4 <= 8; i4 *= 2) { if ((i1 | i2 | i3 | i4) != 0xf) continue; group.push([data[i1], data[i2], data[i3], data[i4]]); } } } } } catch (e) { throw e.message; } return group; };
接下來我們輸入一組數字進行測試:
//測試數據 var test = getGroup(new dataStruct(1, 2, 3, 4)); console.log(test);
測試結果:

在此,可以做一點小優化,去掉重復組合。如果四個數字存在兩個以上的重復,那么就會出現重復的組合,這些重復的組合沒必要多次參與計算,故可以在此去掉。修改后的getGroup方法如下:
getGroup = function (data) { //對dataStruct結構的數字進行排列組合 var group = [], repeat = ''; try { for (var i1 = 1; i1 <= 8; i1 *= 2) { for (var i2 = 1; i2 <= 8; i2 *= 2) { for (var i3 = 1; i3 <= 8; i3 *= 2) { for (var i4 = 1; i4 <= 8; i4 *= 2) { if ((i1 | i2 | i3 | i4) != 0xf) continue; var str = "" + data[i1] + data[i2] + data[i3] + data[i4]; //過濾重復組合 if (repeat.indexOf(str) > -1) continue; repeat += str + ","; group.push([data[i1], data[i2], data[i3], data[i4]]); } } } } } catch (e) { throw e.message; } return group; };
傳入測試數據 1,2,3,3進行測試:(左邊是過濾前的結果,右邊是過濾后的結果)
(過濾前)
(過濾后)
封裝了一個簡單的運算函數(主要應對類似除數為0這種情況):
function operate(f, m, n) { //簡單的計算函數,正常情況返回計算結果,異常情況返回 NaN if (isNaN(m) || isNaN(n)) return NaN; if (f == '*') return (m * n); else if (f == '/') return n ? (m / n) : NaN;//如果除數為0,則返回 NaN else if (f == '-') return (m - n); else return (parseFloat(m) + parseFloat(n)); }
接着寫具體計算(也就是前面說的四個空位):
function compute(a, b, c, d, opt1, opt2, opt3) { ///獲取一組數字的計算結果 ///abcd為4個計算數 ///opt1,opt2,opt3為這四個數依次的運算符號 var result = []; //定義數組保存計算結果 try { //開始根據5種結合方式進行計算 //第一種:{[(A,B)C]D} var r1 = operate(opt1, a, b); var r2 = operate(opt2, r1, c); var r3 = operate(opt3, r2, d); if (!isNaN(r3) && Math.abs((r3 - 24)) < 1e-5) { //由於計算結果可能出現浮點數,這里的比較必須使用浮點數比較方式 result.push('[(' + a + opt1 + b + ')' + opt2 + c + ']' + opt3 + d + '………………1'); } //第二種 {[A(B,C)]D} r1 = operate(opt1, b, c); r2 = operate(opt2, a, r1); r3 = operate(opt3, r2, d); if (!isNaN(r3) && Math.abs((r3 - 24)) < 1e-5) { result.push('[' + a + opt2 + '(' + b + opt1 + c + ')]' + opt3 + d + '………………2'); } //第三種 {A[(B,C)D]} r1 = operate(opt1, b, c); r2 = operate(opt2, r1, d); r3 = operate(opt3, a, r2); if (!isNaN(r3) && Math.abs((r3 - 24)) < 1e-5) { result.push(a + opt3 + '[(' + b + opt1 + c + ')' + opt2 + d + ']………………3'); } //第四種 {A[B(C,D)]} r1 = operate(opt1, c, d); r2 = operate(opt2, b, r1); r3 = operate(opt3, a, r2); if (!isNaN(r3) && Math.abs((r3 - 24)) < 1e-5) { result.push(a + opt3 + '[' + b + opt2 + '(' + c + opt1 + d + ')]………………4'); } //第五種 [(A,B),(C,D)] r1 = operate(opt1, a, b); r2 = operate(opt2, c, d); r3 = operate(opt3, r1, r2); if (!isNaN(r3) && Math.abs((r3 - 24)) < 1e-5) { result.push('(' + a + opt1 + b + ')' + opt3 + '(' + c + opt2 + d + ')………………5'); } } catch (e) { } return result; }
接下來就是向空位中填數字(有簡單的去重操作,但對於復雜的去重,比如 (1+3)*(3+3)與(3+3)*(3+1)還需繼續研究):
function getResult(group) { var result = [], repeat = ''; for (var g = 0; g < group.length; g++) { for (var i = 0; i < 4; i++) { for (var j = 0; j < 4; j++) { for (var k = 0; k < 4; k++) { var a1 = group[g][0], a2 = group[g][1], a3 = group[g][2], a4 = group[g][3]; var tmp = compute(a1, a2, a3, a4, opt[i], opt[j], opt[k]); for (var t = 0; t < tmp.length; t++) { //簡單去重 if (repeat.indexOf(tmp[t]) > -1) { continue; } result.push(tmp[t]); repeat += tmp[t] + ','; } } } } } return result; }
好了,貼出測試效果:
這個是 1、3、3、3

這是 1、2、3、4:

這是5、8、6、5:

最后送上一個比較難計算的:1、4、5、6

以上就是整個實現過程,我沒有按照先開發原型后迭代優化的流程來描述,而是直接貼出了最后的版本,當然這個版本也是相當愚笨的,特別是在去重方面,還需要大大的改進,至於計算速度的話沒啥可擔心的,后續有空還會繼續深入研究。
按照這個思路,其他開發語言要寫出來也不是難事,自己也寫了C# 版本的,此文不再贅述。
再來說說后續可以做的事情吧:
結果去重,這個是首要,也是難點;
UI設計,輸入以及結果展示UI;
可對抗性設計,可以設計成休閑游戲。
作者:喬二哥
如需 apk,請評論區留下郵箱。
本文禁止轉載,特此說明。
