如何逆向獲得計(jì)算24的公式

轉(zhuǎn)載請(qǐng)保留作者和原始連接http://www.reibang.com/p/7768195814cf
2018-06-21更新
------------start------------
添加了Swift和Dart語(yǔ)言的實(shí)現(xiàn)。
https://github.com/aesean/TwentyFour/blob/master/Calculate.swift
https://github.com/aesean/TwentyFour/blob/master/calculate.dart
------------end------------
2018-06-19更新
------------start------------
評(píng)論說(shuō)希望能支持一些特殊規(guī)則計(jì)算惋鹅,代碼已經(jīng)更新了则酝。新代碼是用Kotlin寫(xiě)的,如果需要Java版本闰集,請(qǐng)嘗試用IDE自帶的插件自行轉(zhuǎn)換Java版沽讹。apk的UI沒(méi)有開(kāi)放支持任意數(shù)量的數(shù)字計(jì)算,只是開(kāi)放了支持計(jì)算任意結(jié)果武鲁。需要注意爽雄,有個(gè)非常討厭的情況是除法,如果不做四舍五入沐鼠,1/3*3得到的就不是1了挚瘟。實(shí)際代碼里做了約等,極端情況下可能會(huì)有計(jì)算錯(cuò)誤饲梭。如果你希望能完美精確計(jì)算乘盖,請(qǐng)自行擴(kuò)展MathRuleImpl,
實(shí)際代碼已經(jīng)支持計(jì)算任意數(shù)量的數(shù)字憔涉,以及任何你希望的結(jié)果订框。詳細(xì)請(qǐng)參考下面的源碼:
https://github.com/aesean/TwentyFour/blob/master/app/src/main/java/com/aesean/twentyfour/CalculateImpl.kt
代碼里的main方法示例了5,5兜叨,5穿扳,1四個(gè)數(shù)字計(jì)算結(jié)果的絕對(duì)值小于10的計(jì)算方式。其他規(guī)則請(qǐng)自行嘗試国旷,另外由于Tree的遍歷用到了遞歸矛物,所以如果數(shù)據(jù)個(gè)數(shù)太多有可能會(huì)出現(xiàn)爆棧情況蟋滴,請(qǐng)自行優(yōu)化茂卦。
------------end------------

也可以下載apk恩掷,看下實(shí)際效果帚桩。
https://github.com/aesean/TwentyFour/raw/master/app-release.apk

黑夜給了我黑色的眼睛特漩,我卻用它來(lái)敲代碼吧雹,~笑~。
晚上一個(gè)朋友群里涂身,有人問(wèn):5雄卷,5,5蛤售,1如何計(jì)算24丁鹉。當(dāng)然計(jì)算方式隨手Google一下就有,但這一下子讓我覺(jué)得通過(guò)某種算法肯定可以逆向推出這個(gè)公式悴能。其實(shí)很早就有這個(gè)想法揣钦,就是一直沒(méi)思路,也懶就一直沒(méi)弄漠酿,這次一下心血來(lái)潮就做了下實(shí)現(xiàn)冯凹。
用Java寫(xiě)的,源碼可以參考這里:
https://github.com/aesean/TwentyFour/blob/master/app/src/main/java/com/aesean/twentyfour/TwentyFour.java

這里介紹下思路炒嘲。通常第一眼看到這個(gè)問(wèn)題宇姚,會(huì)非常頭疼,逆向算24的計(jì)算公式夫凸,這怎么搞浑劳。
我們先看下前面:5,5夭拌,5魔熏,1的計(jì)算結(jié)果:(5-(1/5))*5=24。這里仔細(xì)看下啼止,這個(gè)結(jié)果有這樣一種規(guī)律道逗,我們先假定加減乘除的運(yùn)算用'?'代替,無(wú)論最終的計(jì)算公式是什么献烦,結(jié)果一定是a?b=24這種形式滓窍。然后a和b可能是四個(gè)原始數(shù)之一,或者也是a?b這種形式由兩個(gè)數(shù)計(jì)算出來(lái)的巩那。那我們先把a(bǔ)?b這種形式計(jì)算結(jié)果寫(xiě)個(gè)方法出來(lái)吏夯。

private static float[] math(float a, float b) {
    float[] result = new float[6];
    result[0] = a + b;
    result[1] = a - b;
    result[2] = a × b;
    result[3] = a / b;
    return result;
}

這個(gè)方法就是兩個(gè)數(shù)運(yùn)算后所有可能的結(jié)果。這里注意即横,參數(shù)選float是因?yàn)榭赡軙?huì)有上面5-1/5這種情況噪生,int型做除法運(yùn)算會(huì)丟失精度,所以這里強(qiáng)轉(zhuǎn)為float型东囚,避免除法出錯(cuò)跺嗽。這里返回的浮點(diǎn)型數(shù)組就是a與b做加減乘除后的結(jié)果。
好了到了這里其實(shí)我們這個(gè)方法配合排列組合,已經(jīng)可以計(jì)算任意兩個(gè)數(shù)通過(guò)加減乘除如何等于24了桨嫁。直接遍歷排列植兰,然后調(diào)用math返回的結(jié)果判斷是否等于24就可以了。

現(xiàn)在我們需要把兩個(gè)數(shù)的情況璃吧,擴(kuò)展到四個(gè)數(shù)楣导。我們?cè)倏?5-(1/5))*5=24這個(gè)結(jié)果,這個(gè)結(jié)果其實(shí)可以這么理解畜挨,我們可以把最終的結(jié)果看成是a?b?c?d=24筒繁,abcd分別第一,第二巴元,第三毡咏,第四個(gè)數(shù)务冕。第一個(gè)數(shù)與第二個(gè)數(shù)math血当,然后結(jié)果與第三個(gè)數(shù)math,得出的結(jié)果與第四個(gè)數(shù)math臊旭。然后將abcd四個(gè)數(shù),排列出所有可能的排列啥刻,然后就能計(jì)算這四個(gè)數(shù)在這種形式((a?b)?c)?d下的所有可能的結(jié)果了奸鸯。
排列我們寫(xiě)個(gè)方法來(lái)實(shí)現(xiàn),這里不貼源碼了可帽,我寫(xiě)的排列寫(xiě)的比較懶娄涩,也比較爛,只支持4個(gè)數(shù)的排列映跟。其實(shí)這里寫(xiě)的好應(yīng)該是支持任意數(shù)量的數(shù)的排列蓄拣。

private static float[][] arrange(float[] value) {
    .....
    return result;
}

這里返回一個(gè)二維數(shù)組,包含了所有排列結(jié)果努隙。
然后我們借助這個(gè)排列返回的結(jié)果球恤,然后配合math方法就可以計(jì)算所有這種((a?b)?c)?d形式下的計(jì)算結(jié)果了。寫(xiě)成代碼應(yīng)該類(lèi)似下面這樣

public static void start(float[] mNum) {
    float[] value0 = math(mNum[0], mNum[1]);
    // 1-1-1-1
    //當(dāng)三個(gè)數(shù)與一個(gè)數(shù)計(jì)算的時(shí)候
    for (int i = 0; i < value0.length; i++) {
        float[] value1 = math(value0[i], mNum[2]);
        for (int i1 = 0; i1 < value1.length; i1++) {
            float[] value2 = math(value1[i1], mNum[3]);
            for (int i2 = 0; i2 < value2.length; i2++) {
                if (value2[i2] == 24f) {
                     //成功計(jì)算出24
                }
        }
    }
}

先前兩個(gè)數(shù)做math運(yùn)算荸镊,然后跟第三個(gè)數(shù)做math咽斧,然后結(jié)果和第四個(gè)數(shù)做math堪置,那這種形式先不說(shuō)能不能覆蓋全部可能的情況,先看看能不能覆蓋(5-(1/5))*5=24张惹,可以實(shí)際跑下代碼晋柱,發(fā)現(xiàn)并不能。但這里打印結(jié)果發(fā)現(xiàn)可以計(jì)算出-24诵叁,問(wèn)題在哪兒?問(wèn)題出5-(1/5)上钦椭,這里并不是前兩個(gè)數(shù)的結(jié)果與第三個(gè)運(yùn)算拧额,這里是第一個(gè)數(shù)與第二三個(gè)數(shù)的結(jié)果做運(yùn)算。
這里頭疼了彪腔,按照現(xiàn)在的math方法侥锦,我們需要把所有可能的abcd運(yùn)算形式寫(xiě)出來(lái),可能要寫(xiě):(a?(b?c))?d德挣,a?((b?c)?d)恭垦,a?(d?(b?c)),等等好多情況格嗅。比較笨的情況就是將所有結(jié)果全部列舉出來(lái)番挺,一共四個(gè)數(shù)結(jié)果也不是非常多。
但是有個(gè)更加優(yōu)雅的解決方法屯掖。我們?cè)倏聪掠?jì)算結(jié)果玄柏,其中包含24,這表示什么贴铜,這表示把x-y粪摘,換成y-x就可以了。也就是說(shuō)對(duì)于減法運(yùn)算需要處理?yè)Q位的情況绍坝,其實(shí)不光是減法徘意,除法也存在換位問(wèn)題,我們現(xiàn)在計(jì)算方式應(yīng)該還會(huì)計(jì)算出1/24這種情況轩褐。再看
(a?(b?c))?d椎咧,
a?((b?c)?d),
a?(d?(b?c))
這些形勢(shì)灾挨,其實(shí)都是換位邑退。(a?(b?c))?d和a?(d?(b?c))的區(qū)別其實(shí)就是一個(gè)換位,而且如果這兩個(gè)計(jì)算里(a?(b?c))?d的第三個(gè)問(wèn)號(hào)和a?(d?(b?c))的第一個(gè)問(wèn)號(hào)是減法或者除法的時(shí)候才會(huì)出現(xiàn)結(jié)果不一致的情況劳澄。
好了地技,原因明白,那怎么處理呢秒拔?非常簡(jiǎn)單莫矗,改造下math方法就行了。

private static float[] math(float a, float b) {
    float[] result = new float[6];
    result[0] = a + b;
    result[1] = a - b;
    result[2] = a × b;
    result[3] = a / b;
    result[4] = b / a;
    result[5] = b - a;
    return result;
}

我們?cè)趍ath方法里把可能的換位情況加進(jìn)去。
這時(shí)候看下能不能覆蓋到(5-(1/5))5=24作谚,我們一定覆蓋到(1/5-5)5這種情況三娩,這個(gè)很容易明白,但因?yàn)槲覀兗恿藫Q位妹懒,所以第二個(gè)減號(hào)a-b可以被換位為b-a雀监,變成這樣(5-(1/5))5,實(shí)際跑下眨唬,看下效果会前。
成功計(jì)算出24。這時(shí)候其實(shí)已經(jīng)可以覆蓋到很多種情況下的計(jì)算24了匾竿。還漏了一種情況瓦宜。就是((a?b)?c)?d,再加上換位岭妖,無(wú)法解決這種情況(100-96)
(10-6)临庇,這種情況抽象下就是:(a?b)?(c?d)=24,我們前面的運(yùn)算可以解決所有的最后一步是和abcd四個(gè)數(shù)之一做運(yùn)算的情況昵慌,但還有一種是最后是兩個(gè)運(yùn)算結(jié)果之間的運(yùn)算假夺。所以start方法還應(yīng)該加入(a?b)?(c?d)這種情況。

//當(dāng)兩個(gè)數(shù)與兩個(gè)數(shù)計(jì)算的時(shí)候
    float[] rightTwo = math(mNum[2], mNum[3]);
    for (int i = 0; i < value0.length; i++) {
        for (int i1 = 0; i1 < rightTwo.length; i1++) {
            float[] result = math(value0[i], rightTwo[i1]);
            for (int i2 = 0; i2 < result.length; i2++) {
                if (result[i2] == 24f) {
                     // 成功計(jì)算出24
                }
            }
        }
    }

加上后斋攀,我們?cè)儆?jì)算下侄泽,發(fā)現(xiàn)成功解決(100-96)*(10-6)=24了。那我們?cè)傧胂霑?huì)不會(huì)還有沒(méi)有覆蓋到情況蜻韭。我們逆推下悼尾,最終無(wú)論怎么算24都是兩個(gè)數(shù)的?運(yùn)算,然后這兩個(gè)數(shù)要么一個(gè)來(lái)計(jì)算得出肖方,一個(gè)來(lái)自abcd闺魏,要么兩個(gè)都來(lái)自計(jì)算得出。不存在第三種情況俯画。然后針對(duì)第一種里析桥,那個(gè)計(jì)算得出的數(shù),一定是兩個(gè)數(shù)計(jì)算然后和剩下一個(gè)數(shù)做?運(yùn)算艰垂,不存在其他情況泡仗。而第二種的,更簡(jiǎn)單了猜憎,兩個(gè)數(shù)?運(yùn)算娩怎,剩下兩個(gè)數(shù)?運(yùn)算,結(jié)果再?運(yùn)算也不存在其他情況胰柑。好了截亦,現(xiàn)在應(yīng)該是所有情況都覆蓋到了爬泥。
那還有個(gè)問(wèn)題,我們代碼可以跑到 // 成功計(jì)算出24這一行崩瓤,那怎么格式化輸出實(shí)際的公式呢袍啡?也很簡(jiǎn)單,math方法返回的數(shù)組是所有的結(jié)果却桶,而結(jié)果在數(shù)組里的位置代表了計(jì)算時(shí)候所使用的運(yùn)算形式境输。我們可以寫(xiě)這樣一個(gè)方法把index轉(zhuǎn)化為運(yùn)算符號(hào)。

private static String getSymbol(int index) {
    switch (index) {
        case 0:
            return " + ";
        case 1:
            return " - ";
        case 2:
            return " * ";
        case 3:
            return " / ";
        case 4:
            return " / ";
        case 5:
            return " - ";
        default:
            throw new RuntimeException("未知的index類(lèi)型");
    }
}

然后我們通過(guò)start方法里每一層的for循環(huán)的i參數(shù)就可以拿到計(jì)算最終結(jié)果的時(shí)候所使用的符號(hào)了颖系。
代碼類(lèi)似這樣:

String p0, p1, p2;if (i < 4) {
    p0 = LEFT_BRACKETS + (int) mNum[0] + getSymbol(i) + (int) mNum[1] + RIGHT_BRACKETS;
} else {
    p0 = LEFT_BRACKETS + (int) mNum[1] + getSymbol(i) + (int) mNum[0] + RIGHT_BRACKETS;
}
if (i1 < 4) {
    p1 = LEFT_BRACKETS + p0 + getSymbol(i1) + (int) mNum[2] + RIGHT_BRACKETS;
} else {
    p1 = LEFT_BRACKETS + (int) mNum[2] + getSymbol(i1) + p0 + RIGHT_BRACKETS;
}
if (i2 < 4) {
    p2 = p1 + getSymbol(i2) + (int) mNum[3];
} else {
    p2 = (int) mNum[3] + getSymbol(i2) + p1;
}

這里的4寫(xiě)法很不好畴嘶,懶的改了,這里的4就是math方法里的大于4的運(yùn)算都是需要顯示換位的集晚,所以這里需要根據(jù)是否大于4決定顯示輸出是否換位。
到了這里区匣,大致過(guò)程就結(jié)束了偷拔。
如果把前面的思路敲進(jìn)Android,差不多就是這個(gè)樣子亏钩。

Paste_Image.png
Paste_Image.png
Paste_Image.png

還有整個(gè)計(jì)算會(huì)有重復(fù)計(jì)算的情況莲绰,所以會(huì)有結(jié)果除重,還有結(jié)果格式化輸出注意換位等細(xì)節(jié)就不詳細(xì)說(shuō)了姑丑,具體可以直接參考源碼蛤签。

轉(zhuǎn)載請(qǐng)保留作者和原始連接http://www.reibang.com/p/7768195814cf
也可以下載apk,https://github.com/aesean/TwentyFour/raw/master/app-release.apk

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末栅哀,一起剝皮案震驚了整個(gè)濱河市震肮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌留拾,老刑警劉巖戳晌,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異痴柔,居然都是意外死亡沦偎,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)咳蔚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)豪嚎,“玉大人,你說(shuō)我怎么就攤上這事谈火〕扪” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵糯耍,是天一觀(guān)的道長(zhǎng)妄荔。 經(jīng)常有香客問(wèn)我泼菌,道長(zhǎng),這世上最難降的妖魔是什么啦租? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任哗伯,我火速辦了婚禮,結(jié)果婚禮上篷角,老公的妹妹穿的比我還像新娘焊刹。我一直安慰自己,他們只是感情好恳蹲,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布虐块。 她就那樣靜靜地躺著,像睡著了一般嘉蕾。 火紅的嫁衣襯著肌膚如雪贺奠。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,598評(píng)論 1 305
  • 那天错忱,我揣著相機(jī)與錄音儡率,去河邊找鬼。 笑死以清,一個(gè)胖子當(dāng)著我的面吹牛儿普,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掷倔,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼眉孩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了勒葱?” 一聲冷哼從身側(cè)響起浪汪,我...
    開(kāi)封第一講書(shū)人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎凛虽,沒(méi)想到半個(gè)月后吟宦,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涩维,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年殃姓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓦阐。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜗侈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出睡蟋,到底是詐尸還是另有隱情踏幻,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布戳杀,位于F島的核電站该面,受9級(jí)特大地震影響夭苗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜隔缀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一题造、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧猾瘸,春花似錦界赔、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至揽思,卻和暖如春袜腥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背钉汗。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工羹令, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人儡湾。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像执俩,于是被迫代替她去往敵國(guó)和親徐钠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容