帶癩子麻將查表判斷胡牌高效率低內存算法

同事曾問我麻將判定輸贏有沒有什麼高效的方法,他說他隨手寫的三個癩子的情況下判定要6秒多。我當時只想他是需要循環 34 * 34 * 34(共有 34 種麻將) 次並依次判定輸贏,這肯定不是個好方法,後來才意識到不過 39304 次循環,不至於要這麼長時間,問題應該是他判定麻將輸贏的效率略低吧。關於如何優化並減少三個癩子的循環次數後文也有我的想法,反正我答應他嘗試實現下,本文就是整理相關內容。

在我未查閱相關資料時,最初我有兩種想法(本文只深入討論第二種想法)
* 像我當初做鬥地主智能出牌機器人拆解手牌那樣,拆解手牌後判定是否符合條件進而判定輸贏。
* 組合出所有贏的手牌,構造 map,判定輸贏只需查表即可,鍵值初步設想的是排序並拼接成的 string。

查閱資料,知乎 Thinkraft 回答,對我影響很大,不知爲何方法打心底佩服,但是效率並未得到顯著提升(這裏並非沒有提升,可以參考後面測試數據,提升的效率應該源於數據條目的減少吧),可能是 Golang map 查找算法相當高效吧,即便如此採用這種方法可以有效的降低內存佔用,詳細請看我提供的源碼。

麻將共 34 種牌,Wiki-Mahjong 維基-麻將
1 - 9 餅,1 - 9 條,1 - 9 萬,東,南,西,北,紅中,發財,白板(剩餘類型牌與本文算法無關,這裏不予討論)。

var tiles = []byte{
    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // Dots
    0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, // Bamboo
    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, // Characters
    0x31, 0x41, 0x51, 0x61, 0x71, 0x81, 0x91, // East South West North Red Green White
}

麻將若想贏,必須要 4 組 1 對(本文不考慮其它贏的可能,譬如 7 小對,再譬如存在 1 槓/碰的前提下,3 組 1 對即可贏),若想組合出所有贏的手牌,那自然是要找出所有的對和所有的組。
對:共 34 對,每類型均可取 1 對。
組:共 34 + (9 - 2) * 3 組,每類型可取 1 相同牌組有 34 組,餅、條、萬每類型可再取 9 - 2 順序牌組有 21 組,共 55 組。

func findPairs() [][]byte {
    pairs := make([][]byte, 0, len(tiles))

    for _, v := range tiles {
        pair := []byte{v, v}
        pairs = append(pairs, pair)
    }

    return pairs
}

func findGroups() [][]byte {
    groups := make([][]byte, 0, len(tiles)+(9-2)*3)

    // find three identical tiles
    for _, v := range tiles {
        group := []byte{v, v, v}
        groups = append(groups, group)
    }

    // find three sequence tiles
    for i := 2; i < len(tiles); i++ {
        if tiles[i-2]+1 == tiles[i-1] && tiles[i-1] == tiles[i]-1 {
            group := []byte{tiles[i-2], tiles[i-1], tiles[i]}
            groups = append(groups, group)
        }
    }

    return groups
}

雖然找出十分容易,但如何組合我當時着實迷糊了一會,問題出在 55 組裏面 34 組相同牌組在組合的時候同 1 組肯定只能出現 1 次,但是另外 21 組順序牌組在組合的時候同 1 組最多能出現 4 次(玩家就是不想槓呢!),總想着效率至上,但是相同列表裏的組我卻要做不同的處理,我都想過把這 55 組列表拆分成兩個列表,複雜度驟升。最後釋然,當前是數據準備階段,考慮什麼效率,最終拿到正確結果纔是王道。暴力組合即可!!!

通過這個函數校驗手牌有效,直接排序使它變得簡單容易理解,後面你會發現有效的手牌早晚是要排序的。

func checkValid(win []byte) bool {
    sort.Sort(byteSlice(win))

    for i := 4; i < len(win); i++ {
        if win[i] == win[i-4] {
            return false
        }
    }

    return true
}

這裏明確遇到效率問題,是我高估了 Golang 標準庫裏 bytes.Equal() 函數。執行 composeWin 運行時間目測要 1 小時以上(我並未運行完成過,從插入分段日誌猜測時間會很長)。不過也不能怪它,思路本身都存在問題,隨着組合結果越來越多,執行 notExist 代價將越來越大。

func notExist(win []byte, wins [][]byte) bool {
    for _, v := range wins {
        if bytes.Equal(win, v) {
            return false
        }
    }

    return true
}

func composeWin(pairs, groups [][]byte) [][]byte {
    wins := make([][]byte, 0, 11498658)

    tmp := make([]byte, 14)
    for _, pair := range pairs {
        for _, group1 := range groups {
            for _, group2 := range groups {
                for _, group3 := range groups {
                    for _, group4 := range groups {
                        copy(tmp, pair)
                        copy(tmp[2:], group1)
                        copy(tmp[5:], group2)
                        copy(tmp[8:], group3)
                        copy(tmp[11:], group4)

                        if checkValid(tmp) && notExist(tmp, wins) {
                            win := make([]byte, 0, 14)
                            win = append(win, tmp...)
                            wins = append(wins, win)
                        }
                    }
                }
            }
        }
    }

    return wins
}

通過下面這種方法,我將確認是否存在相同贏手牌的工作交給了 Golang map,幾分鐘就可得出結果。
我並未使用 string 類型做 map 鍵類型,其實這個方法並沒有比 string 類型做鍵類型提升多少效率。反而多寫了代碼,增加了複雜度,後文會有測試數據。

type twoUint64 struct {
    H uint64 // High
    L uint64 // Low
}

func composeWinEx(pairs, groups [][]byte) map[twoUint64][]byte {
    wins := make(map[twoUint64][]byte)

    var key twoUint64
    tmp := make([]byte, 14)
    for _, pair := range pairs {
        for _, group1 := range groups {
            for _, group2 := range groups {
                for _, group3 := range groups {
                    for _, group4 := range groups {
                        copy(tmp, pair)
                        copy(tmp[2:], group1)
                        copy(tmp[5:], group2)
                        copy(tmp[8:], group3)
                        copy(tmp[11:], group4)

                        if checkValid(tmp) {
                            key.H = uint64(tmp[0])
                            key.L = uint64(tmp[6])

                            for _, v := range tmp[1:6] {
                                key.H = key.H<<8 + uint64(v)
                            }

                            for _, v := range tmp[7:] {
                                key.L = key.L<<8 + uint64(v)
                            }

                            if _, ok := wins[key]; !ok {
                                win := make([]byte, 0, 14)
                                win = append(win, tmp...)
                                wins[key] = win
                            }
                        }
                    }
                }
            }
        }
    }

    return wins
}

接下來說明 Thinkraft 提出的一位日本人的算法,請讀者儘量去閱讀 Thinkraft 的回答和日本人發佈的 文章,我這裏只對不易理解的地方作補充

判定贏牌時需要注意兩點
* 該相同的要相同
* 該連續的要連續

舉例說明
1 1 1 2 2 2 2 3 3 3 3 4 4 4
2 2 2 3 3 3 3 4 4 4 4 5 5 5
排序後的兩副手牌都是贏,它們看着是否非常相似,如何概括這種贏類型,Thinkraft 把這叫做 牌型

計算相同牌數量,若連續則繼續計算相同牌數量,若不連續中間用數字 0 分割
1 1 1 -> 3
1 1 1 2 2 2 2 -> 3 4(1 2 連續)
1 1 1 2 2 2 2 3 3 3 3 -> 3 4 4(2 3 連續)
1 1 1 2 2 2 2 3 3 3 3 4 4 4 -> 3 4 4 3(3 4 連續)
同理可得 2 2 2 3 3 3 3 4 4 4 4 5 5 5 -> 3 4 4 3

1 1 1 2 3 4 6 7 8 東 東 東 西 西
1 1 1 -> 3
1 1 1 2 -> 3 1(1 2 連續)
1 1 1 2 3 -> 3 1 1(2 3 連續)
1 1 1 2 3 4 -> 3 1 1 1(3 4 連續)
1 1 1 2 3 4 6 -> 3 1 1 1 0 1(4 6 不連續,用 0 分割)
1 1 1 2 3 4 6 7 -> 3 1 1 1 0 1 1(6 7 連續)
1 1 1 2 3 4 6 7 8 -> 3 1 1 1 0 1 1 1(7 8 連續)
1 1 1 2 3 4 6 7 8 東 東 東 -> 3 1 1 1 0 1 1 1 0 3(8 東 不連續,用 0 分割)
1 1 1 2 3 4 6 7 8 東 東 東 西 西 -> 3 1 1 1 0 1 1 1 0 3 0 2(東 西 不連續,用 0 分割)

同理可得 1 2 3 5 6 7 一 二 三 五 六 七 西 西 -> 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 2

接下來是將其二進制化,採用如下規則
1 -> 0
2 -> 1 1 0
3 -> 1 1 1 1 0
4 -> 1 1 1 1 1 1 0
10 -> 1 0
20 -> 1 1 1 0
30 -> 1 1 1 1 1 0
40 -> 1 1 1 1 1 1 1 0
如此編碼的好處就是編碼後每張牌只佔用 1 到 2 位二進制空間,如何理解這點?數字 1 2 3 4 分別代表相同牌數量,舉例來說,規則中 4 或 40 代表了四張相同牌(區別僅是該相同牌是否和後面的連續),編碼後的長度分別是 7 位(1 1 1 1 1 1 0)或 8 位(1 1 1 1 1 1 1 0),7 / 4 = 1.75,8 / 4 = 2,所以每張牌只佔用 1 到 2 位二進制空間啦。

在 Thinkraft 的回答評論裏,有人認爲這是改進的霍夫曼編碼,我順道學習一下霍夫曼編碼。wiki-huffman 維基-霍夫曼
若真按照霍夫曼編碼進行編碼,反而無法保證將 14 張手牌數據存入 int32 裏面,這裏推演一番。

根據方纔計算相同牌數量,不連續以 0 分割,共會出現 0 1 2 3 4 五種字符,粗略統計出現次數如下[4:2755728 2:14386266 3:26038905 0:34871796 1:43069053],將會得到如下霍夫曼編碼
4 -> 0 0 0
2 -> 0 0 1
3 -> 0 1
0 -> 1 0
1 -> 1 1
就拿這個來說 1 2 3 5 6 7 一 二 三 五 六 七 西 西 -> 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 2 ==> 至少要 16 * 2 + 1 * 3 = 35 二進制位,所以無法存入 int32 裏面。

接下來這段就有點偏離原作者的算法啦,主要是我看不懂日文,對原作者這裏的處理不太理解,恰巧 Thinkraft 又未細說這裏,我已在知乎 Thinkraft 回答裏添加評論說明了我的疑問,感興趣的朋友可以去看看,我的知乎用戶名:張聖超,第 30 條評論。

其實理論已經講明瞭,不管原作者是如何想的,我這樣轉成二進制總是沒有錯的
2 2 2 3 3 3 3 4 4 4 4 5 5 5 -> 3 4 4 3 -> 1 1 1 1 0 1 1 1 1 1 1 0 1 1 1 1 1 1 0 1 1 1 1 0
3 -> 1 1 1 1 0
3 4 -> 1 1 1 1 0 1 1 1 1 1 1 0
3 4 4 -> 1 1 1 1 0 1 1 1 1 1 1 0 1 1 1 1 1 1 0
3 4 4 3 -> 1 1 1 1 0 1 1 1 1 1 1 0 1 1 1 1 1 1 0 1 1 1 1 0

1 1 1 2 3 4 6 7 8 東 東 東 西 西 -> 3 1 1 1 0 1 1 1 0 3 0 2 -> 1 1 1 1 0 0 0 1 0 0 0 1 0 1 1 1 1 1 0 1 1 0
1 2 3 5 6 7 一 二 三 五 六 七 西 西 -> 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 2 -> 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 1 1 0
你會發現當轉化成的二進制序列初始爲零時容易產生歧義,高位補 1 進而保留所有有效的二進制位,譬如程序異常你用[2 3 5 6 7 一 二 三 五 六 七 西 西]去調用轉 int32 函數,得出的 int32 數值將會和上面的一致,導致程序將會判定失誤。

以下是我實現的上面所述邏輯
爲了簡化邏輯,我並未以 0 分割,我在需分割處數字直接 +10,如此以來,原作者與我對應如下
1 -> 1(0x01)
2 -> 2(0x02)
3 -> 3(0x03)
4 -> 4(0x04)
10 -> 11(0x0B)
20 -> 12(0x0C)
30 -> 13(0x0D)
40 -> 14(0x0E)
這樣做着實方便後面的 switch 邏輯,會很清晰

func bytesToInt(win []byte) int {
    tmp := make([]byte, 0, 17)
    tmp = append(tmp, 1)
    for i, pos := 1, 0; i < len(win); i++ {
        if win[i-1] == win[i] {
            tmp[pos]++
        } else if win[i-1]+1 == win[i] {
            tmp = append(tmp, 1)
            pos++
        } else {
            tmp = append(tmp, 1)
            tmp[pos] += 0x0A
            pos++
        }
    }

    res := 1
    for _, v := range tmp {
        switch v {
        case 0x01:
            res <<= 1
        case 0x02:
            res <<= 3
            res |= 0x06
        case 0x03:
            res <<= 5
            res |= 0x1E
        case 0x04:
            res <<= 7
            res |= 0x7E
        case 0x0B:
            res <<= 2
            res |= 0x02
        case 0x0C:
            res <<= 4
            res |= 0x0E
        case 0x0D:
            res <<= 6
            res |= 0x3E
        case 0x0E:
            res <<= 8
            res |= 0xFE
        }
    }

    return res
}

下面展示壓力測試結果,不要擔心測試環境,默認的隨機種子,註定它們經歷了相同的手牌
標準 10000000 次和三個癩子 1000 次輸贏判定,統計贏次數,統計用時
int 算法

Test total 10000000, Win 30, Time 59.662091651s
Test total 1000, Win 8, Time 20.836001166s

two uint64 算法

Test total 10000000, Win 30, Time 1m22.517894824s
Test total 1000, Win 8, Time 24.289389045s

string 算法

Test total 10000000, Win 30, Time 1m26.392626271s
Test total 1000, Win 8, Time 24.320570688s

這時它們的效率已相差甚微,就看你想如何使用啦,這裏提一點,不管如何,int 算法是佔用內存最少的算法,在不使用算法轉爲 int 時,佔用內存大約 64 * 2 * 11,498,658 = 1,471,828,224(175M),但是轉爲 int 時,佔用內存大約 32 * 8185 = 261,920(32K),差距就在這裏啦。

三個癩子情況下,如何有效減少循環次數,我是這樣考慮的,借用上面提到的兩點:該相同要相同,該連續的要連續,癩子替換成已存在的牌或是和已存在的牌連續的牌爲最好!細心的人可能會有這樣的擔心,三個癩子本就可以通過變換自成一組,和已存在的牌都不相同,和已存在的牌都不連續,我雖無法證明,但這應該是多慮啦,因爲你以不相同非連續處理癩子都能贏,相同連續處理癩子早就贏了,你可以想幾個例子測驗下。

func availableTiles(win []byte) map[byte]bool {
    available := make(map[byte]bool)

    for _, v := range win {
        if v > 0x01 && v < 0x09 || v > 0x11 && v < 0x19 || v > 0x21 && v < 0x29 {
            available[v-1], available[v+1] = true, true
        }

        available[v] = true
    }

    return available
}

func benchmarkWinEx3Ex(n int, wins map[string][]byte) {
    var win int
    now := time.Now()
    for i := 0; i < n; i++ {
        perm := rand.Perm(136)
        hand := make([]byte, 14)

        for j := 0; j < 14; j++ {
            hand[j] = full[perm[j]]
        }

    EXIT:
        for v1 := range availableTiles(hand[3:]) {
            hand[2] = v1
            for v2 := range availableTiles(hand[2:]) {
                hand[1] = v2
                for v3 := range availableTiles(hand[1:]) {
                    hand[0] = v3

                    tmp := make([]byte, 0, 14)
                    tmp = append(tmp, hand...)

                    if checkValid(tmp) {
                        if _, ok := wins[string(tmp)]; ok {
                            win++
                            break EXIT
                        }
                    }
                }
            }
        }
    }

    fmt.Printf("Test total %d, Win %d, Time %v\n", n, win, time.Since(now))
}

其實上面的邏輯依然可以優化,替換癩子後不用再校驗是否有效,但是效率方面不升反降,畢竟隨機出來的手牌很雜,能觸發到不能替換的牌的情況很少。

func appendAvailableTiles(origin map[byte]int, ap ...byte) map[byte]int {
    available := make(map[byte]int)

    for k, v := range origin {
        available[k] = v
    }

    for _, v := range ap {
        if v > 0x01 && v < 0x09 || v > 0x11 && v < 0x19 || v > 0x21 && v < 0x29 {
            if _, ok := available[v-1]; !ok {
                available[v-1] = 0
            }

            if _, ok := available[v+1]; !ok {
                available[v+1] = 0
            }
        }

        available[v]++
    }

    return available
}

func benchmarkWinEx3Ex2(n int, wins map[string][]byte) {
    var win int
    now := time.Now()
    for i := 0; i < n; i++ {
        perm := rand.Perm(136)
        hand := make([]byte, 14)

        for j := 0; j < 14; j++ {
            hand[j] = full[perm[j]]
        }

        available1 := appendAvailableTiles(nil, hand[3:]...)

    EXIT:
        for v1 := range available1 {
            if available1[v1] >= 4 {
                continue
            }
            available2 := appendAvailableTiles(available1, v1)
            for v2 := range available2 {
                if available2[v2] >= 4 {
                    continue
                }
                available3 := appendAvailableTiles(available2, v2)
                for v3 := range available3 {
                    if available3[v3] >= 4 {
                        continue
                    }
                    tmp := make([]byte, 0, 14)
                    tmp = append(tmp, v1, v2, v3)
                    tmp = append(tmp, hand[3:]...)

                    sort.Sort(byteSlice(tmp))

                    if _, ok := wins[string(tmp)]; ok {
                        win++
                        break EXIT
                    }
                }
            }
        }
    }

    fmt.Printf("Test total %d, Win %d, Time %v\n", n, win, time.Since(now))
}

老方法與需要校驗有效方法效率對比,提升四倍

Test total 10000, Win 56, Time 3m59.80354974s
Test total 10000, Win 56, Time 54.301180241s

兩個新方法效率對比,不升反降

Test total 50000, Win 277, Time 4m34.589149569s
Test total 50000, Win 277, Time 5m18.078941139s

全部源碼

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "log"
    "math/rand"
    "os"
    "sort"
    "time"
)

var tiles = []byte{
    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // Dots
    0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, // Bamboo
    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, // Characters
    0x31, 0x41, 0x51, 0x61, 0x71, 0x81, 0x91, // East South West North Red Green White
}

var full = []byte{
    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // Dots
    0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, // Bamboo
    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, // Characters
    0x31, 0x41, 0x51, 0x61, 0x71, 0x81, 0x91, // East South West North Red Green White
    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // Dots
    0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, // Bamboo
    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, // Characters
    0x31, 0x41, 0x51, 0x61, 0x71, 0x81, 0x91, // East South West North Red Green White
    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // Dots
    0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, // Bamboo
    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, // Characters
    0x31, 0x41, 0x51, 0x61, 0x71, 0x81, 0x91, // East South West North Red Green White
    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // Dots
    0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, // Bamboo
    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, // Characters
    0x31, 0x41, 0x51, 0x61, 0x71, 0x81, 0x91, // East South West North Red Green White
}

func findPairs() [][]byte {
    pairs := make([][]byte, 0, len(tiles))

    for _, v := range tiles {
        pair := []byte{v, v}
        pairs = append(pairs, pair)
    }

    return pairs
}

func findGroups() [][]byte {
    groups := make([][]byte, 0, len(tiles)+(9-2)*3)

    // find three identical tiles
    for _, v := range tiles {
        group := []byte{v, v, v}
        groups = append(groups, group)
    }

    // find three sequence tiles
    for i := 2; i < len(tiles); i++ {
        if tiles[i-2]+1 == tiles[i-1] && tiles[i-1] == tiles[i]-1 {
            group := []byte{tiles[i-2], tiles[i-1], tiles[i]}
            groups = append(groups, group)
        }
    }

    return groups
}

type byteSlice []byte

func (b byteSlice) Len() int {
    return len(b)
}

func (b byteSlice) Less(i, j int) bool {
    return b[i] < b[j]
}

func (b byteSlice) Swap(i, j int) {
    b[i], b[j] = b[j], b[i]
}

func checkValid(win []byte) bool {
    sort.Sort(byteSlice(win))

    for i := 4; i < len(win); i++ {
        if win[i] == win[i-4] {
            return false
        }
    }

    return true
}

func notExist(win []byte, wins [][]byte) bool {
    for _, v := range wins {
        if bytes.Equal(win, v) {
            return false
        }
    }

    return true
}

func composeWin(pairs, groups [][]byte) [][]byte {
    wins := make([][]byte, 0, 11498658)

    tmp := make([]byte, 14)
    for _, pair := range pairs {
        for _, group1 := range groups {
            for _, group2 := range groups {
                for _, group3 := range groups {
                    for _, group4 := range groups {
                        copy(tmp, pair)
                        copy(tmp[2:], group1)
                        copy(tmp[5:], group2)
                        copy(tmp[8:], group3)
                        copy(tmp[11:], group4)

                        if checkValid(tmp) && notExist(tmp, wins) {
                            win := make([]byte, 0, 14)
                            win = append(win, tmp...)
                            wins = append(wins, win)
                        }
                    }
                }
            }
        }
    }

    return wins
}

type twoUint64 struct {
    H uint64 // High
    L uint64 // Low
}

func composeWinEx(pairs, groups [][]byte) map[twoUint64][]byte {
    wins := make(map[twoUint64][]byte)

    var key twoUint64
    tmp := make([]byte, 14)
    for _, pair := range pairs {
        for _, group1 := range groups {
            for _, group2 := range groups {
                for _, group3 := range groups {
                    for _, group4 := range groups {
                        copy(tmp, pair)
                        copy(tmp[2:], group1)
                        copy(tmp[5:], group2)
                        copy(tmp[8:], group3)
                        copy(tmp[11:], group4)

                        if checkValid(tmp) {
                            key.H = uint64(tmp[0])
                            key.L = uint64(tmp[6])

                            for _, v := range tmp[1:6] {
                                key.H = key.H<<8 + uint64(v)
                            }

                            for _, v := range tmp[7:] {
                                key.L = key.L<<8 + uint64(v)
                            }

                            if _, ok := wins[key]; !ok {
                                win := make([]byte, 0, 14)
                                win = append(win, tmp...)
                                wins[key] = win
                            }
                        }
                    }
                }
            }
        }
    }

    return wins
}

type jsonData struct {
    H uint64 // High
    L uint64 // Low
    V []byte // Value
}

func toJSON() {
    pairs := findPairs()

    groups := findGroups()

    wins := composeWinEx(pairs, groups)

    f, err := os.Create("json.data")
    defer f.Close()

    if err != nil {
        log.Fatal("Create", err)
    }

    var jd jsonData

    enc := json.NewEncoder(f)

    for k, v := range wins {
        jd.H = k.H
        jd.L = k.L
        jd.V = v

        if err := enc.Encode(jd); err != nil {
            log.Fatal("Encode", err)
        }
    }
}

func benchmarkWin(n int, wins map[twoUint64][]byte) {
    var win int
    var key twoUint64
    now := time.Now()
    for i := 0; i < n; i++ {
        perm := rand.Perm(136)
        hand := make([]byte, 14)

        for j := 0; j < 14; j++ {
            hand[j] = full[perm[j]]
        }

        sort.Sort(byteSlice(hand))

        key.H = uint64(hand[0])
        key.L = uint64(hand[6])

        for _, v := range hand[1:6] {
            key.H = key.H<<8 + uint64(v)
        }

        for _, v := range hand[7:] {
            key.L = key.L<<8 + uint64(v)
        }

        if _, ok := wins[key]; ok {
            win++
        }
    }

    fmt.Printf("Test total %d, Win %d, Time %v\n", n, win, time.Since(now))
}

func benchmarkWinEx(n int, wins map[twoUint64][]byte) {
    var win int
    var key twoUint64
    now := time.Now()
    for i := 0; i < n; i++ {
        perm := rand.Perm(136)
        hand := make([]byte, 14)

        for j := 0; j < 14; j++ {
            hand[j] = full[perm[j]]
        }

    EXIT:
        for _, v1 := range tiles {
            for _, v2 := range tiles {
                for _, v3 := range tiles {
                    tmp := make([]byte, 0, 14)
                    tmp = append(tmp, v1, v2, v3)
                    tmp = append(tmp, hand[3:]...)

                    if checkValid(tmp) {
                        key.H = uint64(tmp[0])
                        key.L = uint64(tmp[6])

                        for _, v := range tmp[1:6] {
                            key.H = key.H<<8 + uint64(v)
                        }

                        for _, v := range tmp[7:] {
                            key.L = key.L<<8 + uint64(v)
                        }

                        if _, ok := wins[key]; ok {
                            win++
                            break EXIT
                        }
                    }
                }
            }
        }
    }

    fmt.Printf("Test total %d, Win %d, Time %v\n", n, win, time.Since(now))
}

type simpleData struct {
    K int    // Key
    V []byte // Value
}

func bytesToInt(win []byte) int {
    tmp := make([]byte, 0, 17)
    tmp = append(tmp, 1)
    for i, pos := 1, 0; i < len(win); i++ {
        if win[i-1] == win[i] {
            tmp[pos]++
        } else if win[i-1]+1 == win[i] {
            tmp = append(tmp, 1)
            pos++
        } else {
            tmp = append(tmp, 1)
            tmp[pos] += 0x0A
            pos++
        }
    }

    res := 1
    for _, v := range tmp {
        switch v {
        case 0x01:
            res <<= 1
        case 0x02:
            res <<= 3
            res |= 0x06
        case 0x03:
            res <<= 5
            res |= 0x1E
        case 0x04:
            res <<= 7
            res |= 0x7E
        case 0x0B:
            res <<= 2
            res |= 0x02
        case 0x0C:
            res <<= 4
            res |= 0x0E
        case 0x0D:
            res <<= 6
            res |= 0x3E
        case 0x0E:
            res <<= 8
            res |= 0xFE
        }
    }

    return res
}

func toSimple(wins map[twoUint64][]byte) {
    f, err := os.Create("simple.data")
    defer f.Close()

    if err != nil {
        log.Fatal("Create", err)
    }

    var sd simpleData

    enc := json.NewEncoder(f)

    for _, win := range wins {
        sd.K = bytesToInt(win)
        sd.V = win

        if err := enc.Encode(sd); err != nil {
            log.Fatal("Encode", err)
        }
    }
}

func fromSimple() {
    f, err := os.Open("simple.data")
    defer f.Close()

    if err != nil {
        log.Fatal("Open", err)
    }

    var sd simpleData

    dec := json.NewDecoder(f)

    wins := make(map[int]bool)

    for {
        if err := dec.Decode(&sd); err == io.EOF {
            break
        } else if err != nil {
            log.Fatal("Decode", err)
        }

        wins[sd.K] = true
    }

    benchmarkWin2(10000000, wins)
    benchmarkWinEx2(1000, wins)
}

func benchmarkWin2(n int, wins map[int]bool) {
    var win int
    now := time.Now()
    for i := 0; i < n; i++ {
        perm := rand.Perm(136)
        hand := make([]byte, 14)

        for j := 0; j < 14; j++ {
            hand[j] = full[perm[j]]
        }

        sort.Sort(byteSlice(hand))

        if _, ok := wins[bytesToInt(hand)]; ok {
            win++
        }
    }

    fmt.Printf("Test total %d, Win %d, Time %v\n", n, win, time.Since(now))
}

func benchmarkWinEx2(n int, wins map[int]bool) {
    var win int
    now := time.Now()
    for i := 0; i < n; i++ {
        perm := rand.Perm(136)
        hand := make([]byte, 14)

        for j := 0; j < 14; j++ {
            hand[j] = full[perm[j]]
        }

    EXIT:
        for _, v1 := range tiles {
            for _, v2 := range tiles {
                for _, v3 := range tiles {
                    tmp := make([]byte, 0, 14)
                    tmp = append(tmp, v1, v2, v3)
                    tmp = append(tmp, hand[3:]...)

                    if checkValid(tmp) {
                        if _, ok := wins[bytesToInt(tmp)]; ok {
                            win++
                            break EXIT
                        }
                    }
                }
            }
        }
    }

    fmt.Printf("Test total %d, Win %d, Time %v\n", n, win, time.Since(now))
}

func benchmarkWin3(n int, wins map[twoUint64][]byte) {
    winsCopy := make(map[string][]byte)
    for _, v := range wins {
        winsCopy[string(v)] = v
    }

    var win int
    now := time.Now()
    for i := 0; i < n; i++ {
        perm := rand.Perm(136)
        hand := make([]byte, 14)

        for j := 0; j < 14; j++ {
            hand[j] = full[perm[j]]
        }

        sort.Sort(byteSlice(hand))

        if _, ok := winsCopy[string(hand)]; ok {
            win++
        }
    }

    fmt.Printf("Test total %d, Win %d, Time %v\n", n, win, time.Since(now))

    benchmarkWinEx3(1000, winsCopy)
}

func benchmarkWinEx3(n int, wins map[string][]byte) {
    var win int
    now := time.Now()
    for i := 0; i < n; i++ {
        perm := rand.Perm(136)
        hand := make([]byte, 14)

        for j := 0; j < 14; j++ {
            hand[j] = full[perm[j]]
        }

    EXIT:
        for _, v1 := range tiles {
            for _, v2 := range tiles {
                for _, v3 := range tiles {
                    tmp := make([]byte, 0, 14)
                    tmp = append(tmp, v1, v2, v3)
                    tmp = append(tmp, hand[3:]...)

                    if checkValid(tmp) {
                        if _, ok := wins[string(tmp)]; ok {
                            win++
                            break EXIT
                        }
                    }
                }
            }
        }
    }

    fmt.Printf("Test total %d, Win %d, Time %v\n", n, win, time.Since(now))
}

func availableTiles(win []byte) map[byte]bool {
    available := make(map[byte]bool)

    for _, v := range win {
        if v > 0x01 && v < 0x09 || v > 0x11 && v < 0x19 || v > 0x21 && v < 0x29 {
            available[v-1], available[v+1] = true, true
        }

        available[v] = true
    }

    return available
}

func benchmarkWinEx3Ex(n int, wins map[string][]byte) {
    var win int
    now := time.Now()
    for i := 0; i < n; i++ {
        perm := rand.Perm(136)
        hand := make([]byte, 14)

        for j := 0; j < 14; j++ {
            hand[j] = full[perm[j]]
        }

    EXIT:
        for v1 := range availableTiles(hand[3:]) {
            hand[2] = v1
            for v2 := range availableTiles(hand[2:]) {
                hand[1] = v2
                for v3 := range availableTiles(hand[1:]) {
                    hand[0] = v3

                    tmp := make([]byte, 0, 14)
                    tmp = append(tmp, hand...)

                    if checkValid(tmp) {
                        if _, ok := wins[string(tmp)]; ok {
                            win++
                            break EXIT
                        }
                    }
                }
            }
        }
    }

    fmt.Printf("Test total %d, Win %d, Time %v\n", n, win, time.Since(now))
}

func appendAvailableTiles(origin map[byte]int, ap ...byte) map[byte]int {
    available := make(map[byte]int)

    for k, v := range origin {
        available[k] = v
    }

    for _, v := range ap {
        if v > 0x01 && v < 0x09 || v > 0x11 && v < 0x19 || v > 0x21 && v < 0x29 {
            if _, ok := available[v-1]; !ok {
                available[v-1] = 0
            }

            if _, ok := available[v+1]; !ok {
                available[v+1] = 0
            }
        }

        available[v]++
    }

    return available
}

func benchmarkWinEx3Ex2(n int, wins map[string][]byte) {
    var win int
    now := time.Now()
    for i := 0; i < n; i++ {
        perm := rand.Perm(136)
        hand := make([]byte, 14)

        for j := 0; j < 14; j++ {
            hand[j] = full[perm[j]]
        }

        available1 := appendAvailableTiles(nil, hand[3:]...)

    EXIT:
        for v1 := range available1 {
            if available1[v1] >= 4 {
                continue
            }
            available2 := appendAvailableTiles(available1, v1)
            for v2 := range available2 {
                if available2[v2] >= 4 {
                    continue
                }
                available3 := appendAvailableTiles(available2, v2)
                for v3 := range available3 {
                    if available3[v3] >= 4 {
                        continue
                    }
                    tmp := make([]byte, 0, 14)
                    tmp = append(tmp, v1, v2, v3)
                    tmp = append(tmp, hand[3:]...)

                    sort.Sort(byteSlice(tmp))

                    if _, ok := wins[string(tmp)]; ok {
                        win++
                        break EXIT
                    }
                }
            }
        }
    }

    fmt.Printf("Test total %d, Win %d, Time %v\n", n, win, time.Since(now))
}

func main() {
    // fromSimple()
    // return
    f, err := os.Open("json.data")
    defer f.Close()

    if err != nil {
        log.Fatal("Open", err)
    }

    var jd jsonData

    dec := json.NewDecoder(f)

    wins := make(map[twoUint64][]byte)

    for {
        if err := dec.Decode(&jd); err == io.EOF {
            break
        } else if err != nil {
            log.Fatal("Decode", err)
        }

        wins[twoUint64{
            H: jd.H,
            L: jd.L,
        }] = jd.V
    }

    benchmarkWin(10000000, wins)
    benchmarkWinEx(1000, wins)
    //benchmarkWin3(10000000, wins)
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章