emmmmm,好久没发文章了上次发还是在上次
hxdm动动你们发财的小手指点点赞鸭,快没动力了,呜呜呜~
前几天面试,要求做一个类似麻将的游戏的听牌分析。简单说就是找到自己还差什么牌能够胡牌。
一副牌一共有 big(A-H) + small(a-h) + joker(X) 三种类型的牌。
每人手上一共10张牌,X可以变成任意牌。
胡牌方式是:抽取一张牌,达到 n组刻子+ m组顺子 + 1组对子 ; n + m = 3
刻子(triple):
不论大小写的3张相同字母,比如 AAA, AAa, aaA
顺子(straight):
大小写必须相同的3张连续牌, 比如 ABC, abc
对子(pair):
两张大小写相同的牌
胡牌例子:
AGHHXaddgh 听牌:AGag
BGGbcdeefg 听牌:Bb
主要利用的是Python中Counter类;可以理解成升级版的字典,统计所有牌的数量
1、检查手牌是否符合规范:一共10张牌,普通牌每种类型不大于5,joker不大于3,且手牌的范围是A-H,a-z,X
不含X:
2、得到所有提取出一个pair的可能手牌 + 一个都不提的原始手牌
3、将所有的triple 和 straight 提出
两种方式,优先triple,以及优先straight,两种结果取交集
4、根据最后的到的牌的数量以及牌的类型判断差什么牌胡牌
剩一张:差一张组成pair胡牌
剩两张:
两张相同,两张字母相同大小写不同:差一张组成triple
两张相邻,两张间隔为1:差一张组成straight
其余:未听牌
含X的:
在1 2步骤之间加一个步骤:将X转化成为它可能的牌并返回所有结果
话不所说,直接上代码,有详细注释。
import re
import copy
from collections import Counter
# 使用numpy库进行列表扁平化
# import numpy as np
class Hooler:
def __init__(self,
common_nums: int = 5,
joker_nums: int = 3) -> None:
"""
初始化牌堆中的牌
@param common_nums: 每种普通牌的数量,默认为5
@param joker_nums: joker牌的数量,默认为3
"""
self.common_nums = common_nums
self.joker_nums = joker_nums
self.__all_cards = Counter('abcdefghABCDEFGH'*self.common_nums + 'X'*self.joker_nums)
def __check_cards(self, cards: str) -> Counter | bool:
"""
私有方法
检查手牌是否符合规则(一共10张,包含a-h,A-H,X)
@param cards: str类型的手牌
@return: 符合规则返回Counter类型手牌,否则None
"""
if len(cards) != 10:
return False
target = r'[i-zI-WYZ]'
res = re.findall(target, cards)
if res:
return False
counter = Counter(cards)
self.__all_cards -= counter # 总牌堆去掉当前的手牌
if sum(self.__all_cards.values()) != 73:
return False
return counter
def __pair(self, counter: Counter) -> list[Counter]:
"""
私有方法
去掉手牌中的1个pair
@param counter: Counter类型手牌
@return: 所有去掉1个pair后的手牌 + 未去掉pair的手牌
"""
temp = []
res = [counter]
for i in counter:
if counter[i] > 1: # 手牌中是否有数量大于1的种类
temp.append(i)
for i in temp:
counter_copy = copy.deepcopy(counter) # 深拷贝,保证每次只去掉1个
count = {i: 2}
counter_copy -= count # 去掉手牌中一个pair
res.append(counter_copy)
return res
def __pairs(self, counters: [Counter]) -> list[Counter]:
"""
私有方法
主要用于含有X的手牌,去掉手牌中的1个pair
@param counters: 列表类型的Counter手牌
@return: 所有去掉1个pair后的手牌 + 未去掉pair的手牌
"""
res = []
for i in counters:
res.append(self.__pair(i))
res = [i for arr in res for i in arr] # 列表扁平化
return res
def __triple(self, counter: Counter) -> Counter:
"""
私有方法
递归的方式去掉手牌中所有triple
@param counter: Counter类型手牌
@return: 去掉所有triple后的手牌
"""
keys = list(counter.keys())
# 对键按照字母顺序不区分大小写的方式排序
keys.sort(key=lambda x: ord(x) - 32 if ord(x) > 96 else ord(x))
i = 0
while i < len(keys):
# 在边界内比较两张牌是否为一个大写一个小写
if i < len(keys) - 1 and keys[i].upper() == keys[i + 1].upper():
if counter[keys[i]] + counter[keys[i + 1]] >= 3: # 两张牌的和是否大于等于3
if counter[keys[i]] == 1:
count = {
keys[i]: 1,
keys[i + 1]: 2,
}
counter -= count
return self.__triple(counter)
else:
count = {
keys[i]: 2,
keys[i + 1]: 1,
}
counter -= count
return self.__triple(counter)
i += 1
elif counter[keys[i]] >= 3: # 单张牌数大于等于3
count = {keys[i]: 3}
counter -= count
return self.__triple(counter)
i += 1
return counter
def __straight(self, counter: Counter) -> list[Counter]:
"""
私有方法
递归的方式去掉手牌中所有straight
@param counter: Counter类型手牌
@return: 去掉所有straight后的手牌
"""
def help(cards_list: list[list[str]]) -> list[list[str]]:
"""
得到去掉的straight的所有结果
@param cards_list: 手牌
@return: 按不同方法去掉的straight的所有结果
"""
res = []
for cards in cards_list:
remove_card = []
cards_set = list(set(cards))
cards_set.sort()
for i in range(1, len(cards_set) - 1):
# 判断三个字符串是否是相邻的
# counter_copy = copy.deepcopy(counter)
if ord(cards_set[i]) + 1 == ord(cards_set[i + 1]) and\
ord(cards_set[i]) - 1 == ord(cards_set[i - 1]):
remove_card.append(cards_set[i - 1:i + 2])
for i in remove_card:
cards_copy = copy.deepcopy(cards)
for j in i:
cards_copy.remove(j)
res.append(cards_copy)
if not res:
return cards_list
return help(res)
cards = []
for i in counter.keys():
for j in range(counter[i]):
cards.append(i)
cards.sort()
cards_list = help([cards])
res = []
for i in cards_list:
count = Counter(i)
if i not in res:
res.append(count)
return res
# for counter in counters:
# keys = list()
#
# keys.sort()
# counters_copy = [counter for i in range(len(keys) - 2)]
# for i in range(1, len(keys) - 1):
# # 判断三个字符串是否是相邻的
# # counter_copy = copy.deepcopy(counter)
# if ord(keys[i]) + 1 == ord(keys[i + 1]) and ord(keys[i]) - 1 == ord(keys[i - 1]):
# count = {k: 1 for k in keys[i - 1:i + 2]}
# counters_copy[i - 1] -= count
#
# if len(new_counters) == len(counters):
# return new_counters
# return self.__straight(new_counters)
def __check_surplus_cards(self, cards: list[str]) -> list[str]:
"""
检查牌堆中是否还有某种剩余的牌
@param cards: 需要检查的牌
@return: 牌堆中剩余的牌
"""
if len(cards) == 0:
return []
res = []
for i in cards:
if self.__all_cards[i] != 0:
res.append(i)
return res
def __hooler_card(self, counter: Counter) -> list[str]:
"""
判断当前手牌差什么牌能够胡牌
@param counter: Counter类型手牌
@return: 胡的牌
"""
# pair
if sum(counter.values()) == 1:
return self.__check_surplus_cards(list(counter))
# 没有hooler牌
if sum(counter.values()) > 2:
return []
# 剩余两张牌
keys = list(counter.keys())
# triple:两张一样的牌
if len(keys) == 1:
cards = [keys[0].upper(), keys[0]] if ord(keys[0]) >= 97 else [keys[0], keys[0].lower()]
return self.__check_surplus_cards(cards)
# triple:两张大小写不一样的牌
if keys[0].upper() == keys[1].upper():
return self.__check_surplus_cards(keys)
# straight:差首位
if ord(keys[1]) - ord(keys[0]) == 1:
if keys[0] in {'a', 'A'}:
return self.__check_surplus_cards([chr(ord(keys[1]) + 1)])
if keys[1] in {'h', 'H'}:
return self.__check_surplus_cards([chr(ord(keys[0]) - 1)])
return self.__check_surplus_cards([chr(ord(keys[0]) - 1), chr(ord(keys[1]) + 1)])
# straight:差中间
if ord(keys[1]) - ord(keys[0]) == 2:
return self.__check_surplus_cards([chr(ord(keys[0]) + 1)])
def __hooler_x_card(self,
counters: list[Counter],
jokers_num: int) -> list[Counter]:
"""
将万能牌进行替换
@param counters: list[Counter]类型的手牌
@return: 胡的牌
"""
if jokers_num == 0:
return counters
new_counters = []
for counter in counters:
# self.__triple(counter)
# self.__straight(counter)
keys = 'abcdefghABCDEFGH'
# keys = set(counter.keys())
# for i in keys.copy():
# if i != 'X':
# keys.add(chr(ord(i) + 32) if ord(i) < 96 else chr(ord(i) - 32))
# if i not in {'a', 'A', 'X'}:
# x = chr(ord(i) - 1)
# keys.add(x)
# keys.add(chr(ord(x) + 32) if ord(x) < 96 else chr(ord(x) - 32))
# if i not in {'h', 'H', 'X'}:
# x = chr(ord(i) + 1)
# keys.add(x)
# keys.add(chr(ord(x) + 32) if ord(x) < 96 else chr(ord(x) - 32))
for i in keys:
if i != 'X':
counter_copy = copy.deepcopy(counter)
count = {
i: 1,
'X': -1,
}
counter_copy += count
new_counters.append(counter_copy)
return self.__hooler_x_card(new_counters, jokers_num-1)
def reset_all_crads(self) -> None:
"""
重置牌堆
"""
self.__all_cards = Counter('abcdefghABCDEFGH' * self.common_nums + 'X' * self.joker_nums)
def get_hooler_cards(self, cards: str) -> list[str]:
"""
得到当前手牌的胡牌
@param cards: 当前手牌
@return: 能胡的牌无牌胡返回空
"""
self.reset_all_crads() # 每次调用重置一下牌堆
counter = self.__check_cards(cards)
if not counter:
return []
if counter['X'] != 0:
counters = self.__hooler_x_card([counter], counter['X'])
counters = self.__pairs(counters)
else:
counters = self.__pair(counter)
res = []
for i in counters:
i_copy = copy.deepcopy(i)
self.__triple(i)
straight = self.__straight(i)
for j in straight:
temp = self.__hooler_card(j)
if temp:
res.append(temp)
straight = self.__straight(i_copy)
for s in straight:
self.__triple(s)
temp = self.__hooler_card(s)
if temp:
res.append(temp)
# numpy库进行列表扁平化
# res = np.array(res).flatten()
# res = list(res)
res = [i for arr in res for i in arr] # 列表扁平化
res = list(set(res))
res.sort()
return res
def getlist_hooler_cards(self, cards: list[str]) -> list[list[str]]:
"""
得到多组手牌能胡的牌
@param cards: 一组手牌
@return: 能胡的牌
"""
res = []
for i in cards:
temp = self.get_hooler_cards(i)
res.append(temp)
return res