同事曾问我麻将判定输赢有没有什么高效的方法,他说他随手写的三个癞子的情况下判定要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, // Dots0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, // Bamboo0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, // Characters0x31, 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 tilesfor _, v := range tiles {group := []byte{v, v, v}groups = append(groups, group)}// find three sequence tilesfor 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 // HighL uint64 // Low
}func composeWinEx(pairs, groups [][]byte) map[twoUint64][]byte {wins := make(map[twoUint64][]byte)var key twoUint64tmp := 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] += 0x0Apos++}}res := 1for _, v := range tmp {switch v {case 0x01:res <<= 1case 0x02:res <<= 3res |= 0x06case 0x03:res <<= 5res |= 0x1Ecase 0x04:res <<= 7res |= 0x7Ecase 0x0B:res <<= 2res |= 0x02case 0x0C:res <<= 4res |= 0x0Ecase 0x0D:res <<= 6res |= 0x3Ecase 0x0E:res <<= 8res |= 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 intnow := 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] = v1for v2 := range availableTiles(hand[2:]) {hand[1] = v2for v3 := range availableTiles(hand[1:]) {hand[0] = v3tmp := 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 intnow := 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 mainimport ("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 tilesfor _, v := range tiles {group := []byte{v, v, v}groups = append(groups, group)}// find three sequence tilesfor 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 []bytefunc (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 // HighL uint64 // Low
}func composeWinEx(pairs, groups [][]byte) map[twoUint64][]byte {wins := make(map[twoUint64][]byte)var key twoUint64tmp := 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 // HighL uint64 // LowV []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 jsonDataenc := json.NewEncoder(f)for k, v := range wins {jd.H = k.Hjd.L = k.Ljd.V = vif err := enc.Encode(jd); err != nil {log.Fatal("Encode", err)}}
}func benchmarkWin(n int, wins map[twoUint64][]byte) {var win intvar key twoUint64now := 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 intvar key twoUint64now := 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    // KeyV []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] += 0x0Apos++}}res := 1for _, v := range tmp {switch v {case 0x01:res <<= 1case 0x02:res <<= 3res |= 0x06case 0x03:res <<= 5res |= 0x1Ecase 0x04:res <<= 7res |= 0x7Ecase 0x0B:res <<= 2res |= 0x02case 0x0C:res <<= 4res |= 0x0Ecase 0x0D:res <<= 6res |= 0x3Ecase 0x0E:res <<= 8res |= 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 simpleDataenc := json.NewEncoder(f)for _, win := range wins {sd.K = bytesToInt(win)sd.V = winif 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 simpleDatadec := 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 intnow := 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 intnow := 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 intnow := 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 intnow := 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 intnow := 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] = v1for v2 := range availableTiles(hand[2:]) {hand[1] = v2for v3 := range availableTiles(hand[1:]) {hand[0] = v3tmp := 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 intnow := 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()// returnf, err := os.Open("json.data")defer f.Close()if err != nil {log.Fatal("Open", err)}var jd jsonDatadec := 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)
}

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

  1. 高效查表判断胡牌算法的lua版本

    来源于日本论坛的一套用于麻将的判断胡牌算法,运用查表方式实现.原文链接(http://hp.vector.co.jp/authors/VA046927/mjscore/mjalgorism.html) ...

  2. 可带癞子的通用麻将胡牌算法

    本文原创文章,转载注明出处,博客地址 https://segmentfault.com/u/to... 第一时间看后续精彩文章.觉得好的话,顺手分享到朋友圈吧,感谢支持. 笔者前段时间做过一款地方麻将 ...

  3. 麻将胡牌算法,带癞子

    貌似去年去面试一家公司,问了麻将的算法.虽然之前做过广东麻将,但是胡牌算法在服务端,就没有在意. 现在在网上搜了一些算法试了试 = =! 麻将普通的胡牌就是刻子+顺子+将.癞子可以充当任意一张牌. 参 ...

  4. 麻将胡牌算法带癞子 python实现

    姐姐:你去帮我和闺蜜打麻将? 学霸哥哥:可是我不会打麻将呀! 姐姐:你不是学霸吗?我教你一个麻将公式,我闺蜜可是单身哟! 学霸哥哥:什么公式? 姐姐:麻将胡牌公式: AAA*M+ABC*N+BB,WM ...

  5. 癞子麻将胡牌以及听牌算法实现

    最先实现的就是算法的实现. 需求:碰杠胡  ,不能吃 ,不能听 ,只能自摸胡,其中癞子可以做任意牌但是不能碰和杠. 写的时候还不会玩麻将,还是老板教的.^_^ 最麻烦的是胡牌算法.之前搜到的都是不包含 ...

  6. 麻将胡牌判定的判定算法

    麻将胡牌判定的判定算法 问题背景 : 简化了麻将规则,给定[1s-9s],[1b-9b],[1t-9t]一共27种牌,每种牌都有4张.需要判断给定的牌是不是胡牌. 胡牌的定义为: 14张牌里面由一个对 ...

  7. 癞子版本十三张自动摆牌算法

    癞子十三张自动摆牌算法横空出世,早两个月之前发布了非癞子版本十三张摆牌算法,那时候觉得癞子版本很复杂,没有深入研究.后面应粉丝要求出一个癞子版本的试试效果,经过一个月研究,成熟稳定的癞子算法终于完成. ...

  8. 带赖子的麻将胡牌算法Java_带赖子的超高效麻将、跑胡子胡牌算法

    文档 github地址 https://github.com/yuanfengyun/qipai/tree/master/doc lua版 https://github.com/yuanfengyun ...

  9. 带赖子的超高效麻将、跑胡子胡牌算法

    腾讯课堂视频讲解:https://ke.qq.com/course/305608?tuin=104cb0e2 文档 github地址 https://github.com/yuanfengyun/qi ...

最新文章

  1. runaway深度递归函数测试及相关汇编指令
  2. Java 集合类详解
  3. python修改linux网卡信息,linux下修改网卡接口名称
  4. linux中利用脚本编写数组,shell脚本编程之数组
  5. python爬虫找工作要掌握什么_python爬虫实战:判断招聘信息的存在
  6. 福建地震局网站被******挂壮阳广告
  7. mssql mysql数据库大小_mssql查看数据库大小
  8. HashMap及ConcurrentHashMap基本原理概述
  9. 小鹏、蔚来相继车祸,自动驾驶四字不等于“全自动”,也分等级!
  10. apache http php,性能-安装HTTP使用PHP和Apache使标头失效
  11. [nvidia] CUDA_VISIBLE_DEVICES
  12. lua与c若干问题 - 专职C++ - C++博客
  13. 配音鸭 是什么 从哪儿进入 如何使用 手把手指南来了
  14. python画圣诞帽_用Python给头像戴圣诞帽
  15. 论文笔记(三):DAML: Dual Attention Mutual Learning between Ratings and Reviews for Item Recommendation
  16. 好用的工作邮箱有哪些?工作邮箱如何登录
  17. 由设计稿一键智能生成代码的快捷软件
  18. android开源轮播图框架,最好用的轮播插件框架都在这里了,Github流行轮播框架...
  19. 逆水寒 各个服务器位置,从商人的角度看逆水寒——服务器选择篇
  20. -Dmaven.multiModuleProjectDirectory system property is not set. Check $M2_HOME e

热门文章

  1. ffmpeg截取视频流、rtsp录mp4,与海思NVR,内存问题
  2. USB转串口芯片 FT232RL/CH9101/PL2303/CY7C65213同类型芯片参数对比
  3. 【Python应用探索笔记二】百度语音合成与识别
  4. vue + echars地图 省市区 + 添加点标记
  5. 06.01、js之日期获取方法-年月日时分秒毫秒星期
  6. MTK Android Driver知识大全
  7. VGG-传统神经网络之巅峰
  8. Sonar编译问题对应:File [...] can't be indexed twice.
  9. 怎么隐藏label标签
  10. 计算机表格单元格合并,excel表格数据拆分和合并单元格-excel中如何将已经合并的单元格拆分,并将该单元格......