题目重点:把牌型匹配、牌型优先级和同牌型数字比较拆开;add_type 注册判定函数,evaluate 接收 evaluation_orders。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 代码只演示四张、无花色、点数 2..14 的 rank-only 训练模型,以便还原 8883 与 7773 的可见例子;原题是否四张、是否有花色/顺子及完整牌型顺序均未公开。
- evaluation_orders 在训练版由强到弱,遇到首个匹配牌型即用其级别;同牌型按出现次数最多的组、组内点数、其余 kicker 字典序比较。这是一种自定 tie rule,不等同完整德扑或 Camel Cards。
- 自定义 matches_function 仅返回布尔值;若新牌型需要专属 tie break,应扩成 matcher 返回结构化 rank,而非强套通用分组规则。
解题路线
- 先问手牌数量、花色、Joker、牌型全集、牌型是否可能同时匹配、平局语义和 evaluation_orders 方向;这些会直接影响正确性。
- 把 `matches_function` 注册在 type_id 映射里,拒绝重复 ID;evaluate 根据本次订单从强到弱测试,每手选择首个匹配类型。将类型匹配与同型数字比较隔离,避免新增牌型时改长串 if/else。
- 在可见 8883 对 7773 例中,两手同为三条。训练比较键先看 (3,8) 与 (3,7),所以前者获胜。若类型不同,以 evaluation_orders 的位置先分胜负,不让高数字的弱牌型越级。
- 若题目允许动态变更类型或多线程,注册表应带不可变版本/快照;evaluate 全程使用同一版本。若某 matcher 出错、超时或有副作用,需约束插件接口并把失败显式返回。
手动走一遍
注册 three、pair、high,并设强到弱顺序。8883 与 7773 都匹配 three,数字键分别为 (3,8,1,3) 与 (3,7,1,3),前者胜;2234 对 5432 分别为 pair/high,pair 胜,即使后者最大单牌更大。训练代码不处理花色。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
"""An explicit rank-only training model for the Rippling card OOD prompt.
No original hand size, suit rules, full type order, or tie-break contract was
published. The article explains how these choices differ from real poker.
"""
from collections import Counter
from collections.abc import Callable, Sequence
Hand = tuple[int, ...]
class HandEvaluator:
def __init__(self) -> None:
self._types: dict[str, Callable[[Hand], bool]] = {}
def add_type(self, type_id: str, matches_function: Callable[[Hand], bool]) -> None:
if not type_id or type_id in self._types:
raise ValueError("type id must be unique and nonempty")
self._types[type_id] = matches_function
@staticmethod
def _tie_key(hand: Hand) -> tuple[int, ...]:
# First compare largest equal-rank groups, then their rank, then kickers.
groups = sorted(((count, rank) for rank, count in Counter(hand).items()), reverse=True)
return tuple(number for group in groups for number in group)
def evaluate(
self, first: Sequence[int], second: Sequence[int], evaluation_orders: Sequence[str]
) -> int:
"""Return 1 if first wins, -1 if second wins, 0 if tied.
evaluation_orders lists strongest type first. At least one listed type
must match each hand, usually a catch-all high_card matcher.
"""
a, b = tuple(first), tuple(second)
if len(a) != len(b) or not a or any(not 2 <= value <= 14 for value in a + b):
raise ValueError("equal nonempty rank-only hands with ranks 2..14 required")
if len(set(evaluation_orders)) != len(evaluation_orders):
raise ValueError("duplicate evaluation type")
if any(name not in self._types for name in evaluation_orders):
raise ValueError("unknown evaluation type")
def key(hand: Hand) -> tuple[int, tuple[int, ...]]:
for position, name in enumerate(evaluation_orders):
if self._types[name](hand):
return -position, self._tie_key(hand)
raise ValueError("no evaluation type matches a hand")
ka, kb = key(a), key(b)
return (ka > kb) - (ka < kb)
if __name__ == "__main__":
game = HandEvaluator()
game.add_type("three", lambda hand: 3 in Counter(hand).values())
game.add_type("pair", lambda hand: 2 in Counter(hand).values())
game.add_type("high", lambda hand: True)
order = ["three", "pair", "high"]
assert game.evaluate([8, 8, 8, 3], [7, 7, 7, 3], order) == 1
assert game.evaluate([2, 2, 3, 4], [5, 4, 3, 2], order) == 1
复杂度
H 手牌数、T 注册类型数、每手 C 张牌;朴素 evaluate 最多测试 2T 个 matcher,若每个扫描 C 张,O(TC),分组排序 O(C log C)。空间 O(T+C);matcher 若自带复杂逻辑需另计。
易错点与追问
容易失分的边界
- 把特定牌型强弱硬编码,新增 type_id 后仍需改 evaluate。
- 先比较最大点数,再看牌型,导致高牌赢三条。
- 看到 Camel Cards 字样就擅自补 Joker、花色或完整扑克规则。
- 不处理重叠牌型:三条也可能匹配一个宽泛的 pair predicate。
- 假设通用 tie key 对所有自定义牌型都正确。
继续深挖
- 用户注册的 matcher 同时匹配多个类型时谁胜?
- 新牌型有独特同型比较规则时 API 怎么扩展?
- 牌型顺序每次请求不同,是否缓存结果?
- Joker 或花色加入后数据模型如何修改?
