24點計算器的Javascript實現


前段時間小舅子(小學生)過來玩,沒事一起玩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);

 

測試結果:

1234排列組合

在此,可以做一點小優化,去掉重復組合。如果四個數字存在兩個以上的重復,那么就會出現重復的組合,這些重復的組合沒必要多次參與計算,故可以在此去掉。修改后的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

1333

這是 1、2、3、4:

1234

這是5、8、6、5:

5865

 

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

教難的 1456

以上就是整個實現過程,我沒有按照先開發原型后迭代優化的流程來描述,而是直接貼出了最后的版本,當然這個版本也是相當愚笨的,特別是在去重方面,還需要大大的改進,至於計算速度的話沒啥可擔心的,后續有空還會繼續深入研究。

按照這個思路,其他開發語言要寫出來也不是難事,自己也寫了C# 版本的,此文不再贅述。

再來說說后續可以做的事情吧:

結果去重,這個是首要,也是難點;

UI設計,輸入以及結果展示UI;

可對抗性設計,可以設計成休閑游戲。

作者:喬二哥

如需 apk,請評論區留下郵箱。

本文禁止轉載,特此說明。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM