带癞子麻将查表判断胡牌高效率低内存算法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/panshiqu/article/details/58610958

同事曾问我麻将判定输赢有没有什么高效的方法,他说他随手写的三个癞子的情况下判定要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)
}

猜你喜欢

转载自blog.csdn.net/panshiqu/article/details/58610958