扑克牌游戏是世界上最流行的纸牌游戏之一,具有复杂的规则和策略。在 Exercism 的 "poker" 练习中,我们需要实现一个函数来确定给定扑克牌手牌中的获胜者。这不仅能帮助我们掌握复杂规则的实现和比较算法,还能深入学习Rust中的枚举、模式匹配、排序和算法设计。
什么是扑克牌游戏?
扑克牌游戏有许多变种,其中最流行的是德州扑克。在这个练习中,我们需要实现标准扑克牌游戏中各种牌型的比较规则。扑克牌的牌型从低到高依次为:
- 高牌(High Card):没有形成任何特定组合的牌
- 一对(One Pair):两张相同点数的牌
- 两对(Two Pair):两个不同点数的对子
- 三条(Three of a Kind):三张相同点数的牌
- 顺子(Straight):五张连续点数的牌
- 同花(Flush):五张相同花色的牌
- 葫芦(Full House):三条加一对
- 四条(Four of a Kind):四张相同点数的牌
- 同花顺(Straight Flush):五张相同花色的连续牌
- 皇家同花顺(Royal Flush):10、J、Q、K、A的同花顺
让我们先看看练习提供的函数签名:
rust
/// Given a list of poker hands, return a list of those hands which win.
///
/// Note the type signature: this function should return _the same_ reference to
/// the winning hand(s) as were passed in, not reconstructed strings which happen to be equal.
pub fn winning_hands<'a>(hands: &[&'a str]) -> Vec<&'a str> {
unimplemented!("Out of {:?}, which hand wins?", hands)
}
我们需要实现 winning_hands 函数,从给定的扑克牌手牌中找出获胜的手牌。
设计分析
1. 核心要求
- 牌面解析:正确解析字符串表示的扑克牌
- 牌型识别:识别手牌的牌型并进行分类
- 牌型比较:比较不同手牌的牌型和点数
- 平局处理:正确处理多个获胜者的情况
2. 技术要点
- 枚举设计:设计合适的枚举表示牌型和牌面
- 模式匹配:使用模式匹配处理复杂的比较逻辑
- 排序算法:实现牌面的排序和比较
- 引用处理:正确处理字符串引用,返回原始引用
完整实现
1. 基础实现
rust
use std::cmp::Ordering;
use std::collections::HashMap;
/// Given a list of poker hands, return a list of those hands which win.
///
/// Note the type signature: this function should return _the same_ reference to
/// the winning hand(s) as were passed in, not reconstructed strings which happen to be equal.
pub fn winning_hands<'a>(hands: &[&'a str]) -> Vec<&'a str> {
if hands.is_empty() {
return vec![];
}
// 将字符串手牌转换为Hand结构体
let mut parsed_hands: Vec<(Hand, &str)> = hands
.iter()
.map(|&hand_str| (Hand::from_str(hand_str), hand_str))
.collect();
// 按照牌力排序(降序)
parsed_hands.sort_by(|a, b| b.0.cmp(&a.0));
// 找到所有获胜的手牌(与最高牌力相同的手牌)
let winning_hand = &parsed_hands[0].0;
parsed_hands
.into_iter()
.take_while(|(hand, _)| hand == winning_hand)
.map(|(_, hand_str)| hand_str)
.collect()
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Card {
rank: Rank,
suit: Suit,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
enum Rank {
Two = 2,
Three = 3,
Four = 4,
Five = 5,
Six = 6,
Seven = 7,
Eight = 8,
Nine = 9,
Ten = 10,
Jack = 11,
Queen = 12,
King = 13,
Ace = 14,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
enum Suit {
Clubs,
Diamonds,
Hearts,
Spades,
}
impl Card {
fn from_str(s: &str) -> Card {
let chars: Vec<char> = s.chars().collect();
if chars.len() < 2 {
panic!("Invalid card string: {}", s);
}
let rank = match chars[0] {
'2' => Rank::Two,
'3' => Rank::Three,
'4' => Rank::Four,
'5' => Rank::Five,
'6' => Rank::Six,
'7' => Rank::Seven,
'8' => Rank::Eight,
'9' => Rank::Nine,
'1' if chars.len() >= 2 && chars[1] == '0' => Rank::Ten,
'T' | '0' => Rank::Ten,
'J' => Rank::Jack,
'Q' => Rank::Queen,
'K' => Rank::King,
'A' => Rank::Ace,
_ => panic!("Invalid rank in card string: {}", s),
};
let suit_char = if chars[0] == '1' && chars.len() >= 2 && chars[1] == '0' {
// 处理"10"的情况
if chars.len() >= 3 {
chars[2]
} else {
panic!("Invalid card string: {}", s);
}
} else {
chars[chars.len() - 1]
};
let suit = match suit_char {
'C' => Suit::Clubs,
'D' => Suit::Diamonds,
'H' => Suit::Hearts,
'S' => Suit::Spades,
_ => panic!("Invalid suit in card string: {}", s),
};
Card { rank, suit }
}
}
#[derive(Debug, Clone)]
struct Hand {
cards: Vec<Card>,
hand_type: HandType,
rank_counts: HashMap<Rank, usize>,
sorted_ranks: Vec<Rank>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum HandType {
HighCard,
OnePair,
TwoPair,
ThreeOfAKind,
Straight,
Flush,
FullHouse,
FourOfAKind,
StraightFlush,
}
impl Hand {
fn from_str(hand_str: &str) -> Hand {
let card_strs: Vec<&str> = hand_str.split_whitespace().collect();
let cards: Vec<Card> = card_strs.iter().map(|&s| Card::from_str(s)).collect();
if cards.len() != 5 {
panic!("Invalid hand: must contain exactly 5 cards");
}
let mut rank_counts = HashMap::new();
for card in &cards {
*rank_counts.entry(card.rank.clone()).or_insert(0) += 1;
}
let hand_type = Self::determine_hand_type(&cards, &rank_counts);
let mut sorted_ranks: Vec<Rank> = cards.iter().map(|c| c.rank.clone()).collect();
sorted_ranks.sort_by(|a, b| b.cmp(a)); // 降序排列
Hand {
cards,
hand_type,
rank_counts,
sorted_ranks,
}
}
fn determine_hand_type(cards: &[Card], rank_counts: &HashMap<Rank, usize>) -> HandType {
let is_flush = cards.iter().all(|card| card.suit == cards[0].suit);
let mut ranks: Vec<Rank> = cards.iter().map(|c| c.rank.clone()).collect();
ranks.sort();
let is_straight = Self::is_straight(&ranks);
// 特殊情况:A,2,3,4,5 是顺子
let is_low_straight = ranks == vec![Rank::Two, Rank::Three, Rank::Four, Rank::Five, Rank::Ace];
if is_flush && (is_straight || is_low_straight) {
return HandType::StraightFlush;
}
if is_flush {
return HandType::Flush;
}
if is_straight || is_low_straight {
return HandType::Straight;
}
let counts: Vec<usize> = rank_counts.values().cloned().collect();
let mut count_counts = HashMap::new();
for &count in &counts {
*count_counts.entry(count).or_insert(0) += 1;
}
if count_counts.get(&4).unwrap_or(&0) == &1 {
HandType::FourOfAKind
} else if count_counts.get(&3).unwrap_or(&0) == &1 && count_counts.get(&2).unwrap_or(&0) == &1 {
HandType::FullHouse
} else if count_counts.get(&3).unwrap_or(&0) == &1 {
HandType::ThreeOfAKind
} else if count_counts.get(&2).unwrap_or(&0) == &2 {
HandType::TwoPair
} else if count_counts.get(&2).unwrap_or(&0) == &1 {
HandType::OnePair
} else {
HandType::HighCard
}
}
fn is_straight(ranks: &[Rank]) -> bool {
if ranks.len() != 5 {
return false;
}
for i in 0..4 {
if (ranks[i + 1] as u8) != (ranks[i] as u8) + 1 {
return false;
}
}
true
}
}
impl PartialEq for Hand {
fn eq(&self, other: &Self) -> bool {
self.hand_type == other.hand_type && self.sorted_ranks == other.sorted_ranks
}
}
impl Eq for Hand {}
impl PartialOrd for Hand {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Hand {
fn cmp(&self, other: &Self) -> Ordering {
// 首先比较牌型
let hand_type_order = |hand_type: &HandType| -> u8 {
match hand_type {
HandType::HighCard => 0,
HandType::OnePair => 1,
HandType::TwoPair => 2,
HandType::ThreeOfAKind => 3,
HandType::Straight => 4,
HandType::Flush => 5,
HandType::FullHouse => 6,
HandType::FourOfAKind => 7,
HandType::StraightFlush => 8,
}
};
let self_type_order = hand_type_order(&self.hand_type);
let other_type_order = hand_type_order(&other.hand_type);
match self_type_order.cmp(&other_type_order) {
Ordering::Equal => {
// 牌型相同时,比较具体的牌力
self.compare_same_type(other)
}
other => other,
}
}
}
impl Hand {
fn compare_same_type(&self, other: &Self) -> Ordering {
match self.hand_type {
HandType::HighCard | HandType::Flush | HandType::Straight | HandType::StraightFlush => {
// 比较最高牌,然后依次比较
for (self_rank, other_rank) in self.sorted_ranks.iter().zip(other.sorted_ranks.iter()) {
match self_rank.cmp(other_rank) {
Ordering::Equal => continue,
other => return other,
}
}
Ordering::Equal
}
HandType::OnePair => {
// 比较对子的点数
let self_pair_rank = self.get_rank_by_count(2);
let other_pair_rank = other.get_rank_by_count(2);
match self_pair_rank.cmp(&other_pair_rank) {
Ordering::Equal => {
// 对子相同,比较剩余牌
self.compare_kickers(&[self_pair_rank], &other, &[other_pair_rank])
}
other => other,
}
}
HandType::TwoPair => {
// 比较两对的点数(从高到低)
let mut self_pairs: Vec<Rank> = self.get_ranks_by_count(2);
let mut other_pairs: Vec<Rank> = other.get_ranks_by_count(2);
self_pairs.sort_by(|a, b| b.cmp(a));
other_pairs.sort_by(|a, b| b.cmp(a));
for (self_rank, other_rank) in self_pairs.iter().zip(other_pairs.iter()) {
match self_rank.cmp(other_rank) {
Ordering::Equal => continue,
other => return other,
}
}
// 两对都相同,比较单牌
self.compare_kickers(&self_pairs, &other, &other_pairs)
}
HandType::ThreeOfAKind => {
// 比较三条的点数
let self_three_rank = self.get_rank_by_count(3);
let other_three_rank = other.get_rank_by_count(3);
match self_three_rank.cmp(&other_three_rank) {
Ordering::Equal => {
// 三条相同,比较剩余牌
self.compare_kickers(&[self_three_rank], &other, &[other_three_rank])
}
other => other,
}
}
HandType::FullHouse => {
// 比较三条的点数
let self_three_rank = self.get_rank_by_count(3);
let other_three_rank = other.get_rank_by_count(3);
match self_three_rank.cmp(&other_three_rank) {
Ordering::Equal => {
// 三条相同,比较对子
let self_pair_rank = self.get_rank_by_count(2);
let other_pair_rank = other.get_rank_by_count(2);
self_pair_rank.cmp(&other_pair_rank)
}
other => other,
}
}
HandType::FourOfAKind => {
// 比较四条的点数
let self_four_rank = self.get_rank_by_count(4);
let other_four_rank = other.get_rank_by_count(4);
match self_four_rank.cmp(&other_four_rank) {
Ordering::Equal => {
// 四条相同,比较剩余牌
self.compare_kickers(&[self_four_rank], &other, &[other_four_rank])
}
other => other,
}
}
}
}
fn get_rank_by_count(&self, count: usize) -> Rank {
self.rank_counts
.iter()
.find(|(_, &c)| c == count)
.map(|(rank, _)| rank.clone())
.unwrap()
}
fn get_ranks_by_count(&self, count: usize) -> Vec<Rank> {
self.rank_counts
.iter()
.filter(|(_, &c)| c == count)
.map(|(rank, _)| rank.clone())
.collect()
}
fn compare_kickers(&self, exclude_ranks: &[Rank], other: &Self, other_exclude_ranks: &[Rank]) -> Ordering {
let mut self_ranks: Vec<Rank> = self.sorted_ranks.clone();
let mut other_ranks: Vec<Rank> = other.sorted_ranks.clone();
// 移除需要排除的点数
self_ranks.retain(|r| !exclude_ranks.contains(r));
other_ranks.retain(|r| !other_exclude_ranks.contains(r));
// 比较剩余牌
for (self_rank, other_rank) in self_ranks.iter().zip(other_ranks.iter()) {
match self_rank.cmp(other_rank) {
Ordering::Equal => continue,
other => return other,
}
}
Ordering::Equal
}
}
2. 优化实现
rust
use std::cmp::Ordering;
use std::collections::HashMap;
/// Given a list of poker hands, return a list of those hands which win.
///
/// Note the type signature: this function should return _the same_ reference to
/// the winning hand(s) as were passed in, not reconstructed strings which happen to be equal.
pub fn winning_hands<'a>(hands: &[&'a str]) -> Vec<&'a str> {
if hands.is_empty() {
return vec![];
}
// 将字符串手牌转换为Hand结构体
let mut parsed_hands: Vec<(Hand, &str)> = hands
.iter()
.map(|&hand_str| (Hand::from_str(hand_str), hand_str))
.collect();
// 按照牌力排序(降序)
parsed_hands.sort_unstable_by(|a, b| b.0.cmp(&a.0));
// 找到所有获胜的手牌(与最高牌力相同的手牌)
let winning_hand = &parsed_hands[0].0;
parsed_hands
.into_iter()
.take_while(|(hand, _)| hand.cmp(winning_hand) == Ordering::Equal)
.map(|(_, hand_str)| hand_str)
.collect()
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Card {
rank: Rank,
suit: Suit,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
enum Rank {
Two = 2,
Three = 3,
Four = 4,
Five = 5,
Six = 6,
Seven = 7,
Eight = 8,
Nine = 9,
Ten = 10,
Jack = 11,
Queen = 12,
King = 13,
Ace = 14,
}
impl Rank {
fn from_char(c: char) -> Option<Rank> {
match c {
'2' => Some(Rank::Two),
'3' => Some(Rank::Three),
'4' => Some(Rank::Four),
'5' => Some(Rank::Five),
'6' => Some(Rank::Six),
'7' => Some(Rank::Seven),
'8' => Some(Rank::Eight),
'9' => Some(Rank::Nine),
'T' | '0' => Some(Rank::Ten),
'J' => Some(Rank::Jack),
'Q' => Some(Rank::Queen),
'K' => Some(Rank::King),
'A' => Some(Rank::Ace),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
enum Suit {
Clubs,
Diamonds,
Hearts,
Spades,
}
impl Suit {
fn from_char(c: char) -> Option<Suit> {
match c {
'C' => Some(Suit::Clubs),
'D' => Some(Suit::Diamonds),
'H' => Some(Suit::Hearts),
'S' => Some(Suit::Spades),
_ => None,
}
}
}
impl Card {
fn from_str(s: &str) -> Card {
let chars: Vec<char> = s.chars().collect();
if chars.is_empty() {
panic!("Invalid card string: {}", s);
}
let (rank_char, suit_char) = if chars.len() >= 3 && chars[0] == '1' && chars[1] == '0' {
// 处理"10"的情况
if chars.len() >= 3 {
('0', chars[2])
} else {
panic!("Invalid card string: {}", s);
}
} else if chars.len() >= 2 {
(chars[0], chars[chars.len() - 1])
} else {
panic!("Invalid card string: {}", s);
};
let rank = Rank::from_char(rank_char)
.unwrap_or_else(|| panic!("Invalid rank in card string: {}", s));
let suit = Suit::from_char(suit_char)
.unwrap_or_else(|| panic!("Invalid suit in card string: {}", s));
Card { rank, suit }
}
}
#[derive(Debug, Clone)]
struct Hand {
cards: Vec<Card>,
hand_type: HandType,
rank_counts: HashMap<Rank, usize>,
sorted_ranks: Vec<Rank>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum HandType {
HighCard,
OnePair,
TwoPair,
ThreeOfAKind,
Straight,
Flush,
FullHouse,
FourOfAKind,
StraightFlush,
}
impl HandType {
fn rank(&self) -> u8 {
match self {
HandType::HighCard => 0,
HandType::OnePair => 1,
HandType::TwoPair => 2,
HandType::ThreeOfAKind => 3,
HandType::Straight => 4,
HandType::Flush => 5,
HandType::FullHouse => 6,
HandType::FourOfAKind => 7,
HandType::StraightFlush => 8,
}
}
}
impl Hand {
fn from_str(hand_str: &str) -> Hand {
let card_strs: Vec<&str> = hand_str.split_whitespace().collect();
if card_strs.len() != 5 {
panic!("Invalid hand: must contain exactly 5 cards");
}
let cards: Vec<Card> = card_strs.iter().map(|&s| Card::from_str(s)).collect();
let mut rank_counts = HashMap::new();
for card in &cards {
*rank_counts.entry(card.rank.clone()).or_insert(0) += 1;
}
let hand_type = Self::determine_hand_type(&cards, &rank_counts);
let mut sorted_ranks: Vec<Rank> = cards.iter().map(|c| c.rank.clone()).collect();
sorted_ranks.sort_by(|a, b| b.cmp(a)); // 降序排列
Hand {
cards,
hand_type,
rank_counts,
sorted_ranks,
}
}
fn determine_hand_type(cards: &[Card], rank_counts: &HashMap<Rank, usize>) -> HandType {
let is_flush = cards.iter().all(|card| card.suit == cards[0].suit);
let mut ranks: Vec<Rank> = cards.iter().map(|c| c.rank.clone()).collect();
ranks.sort();
let is_straight = Self::is_straight(&ranks);
// 特殊情况:A,2,3,4,5 是顺子
let is_low_straight = ranks == vec![Rank::Two, Rank::Three, Rank::Four, Rank::Five, Rank::Ace];
if is_flush && (is_straight || is_low_straight) {
return HandType::StraightFlush;
}
let counts: Vec<usize> = rank_counts.values().cloned().collect();
let mut count_counts = HashMap::new();
for &count in &counts {
*count_counts.entry(count).or_insert(0) += 1;
}
if count_counts.get(&4).unwrap_or(&0) == &1 {
HandType::FourOfAKind
} else if count_counts.get(&3).unwrap_or(&0) == &1 && count_counts.get(&2).unwrap_or(&0) == &1 {
HandType::FullHouse
} else if is_flush {
HandType::Flush
} else if is_straight || is_low_straight {
HandType::Straight
} else if count_counts.get(&3).unwrap_or(&0) == &1 {
HandType::ThreeOfAKind
} else if count_counts.get(&2).unwrap_or(&0) == &2 {
HandType::TwoPair
} else if count_counts.get(&2).unwrap_or(&0) == &1 {
HandType::OnePair
} else {
HandType::HighCard
}
}
fn is_straight(ranks: &[Rank]) -> bool {
if ranks.len() != 5 {
return false;
}
for i in 0..4 {
if (ranks[i + 1] as u8) != (ranks[i] as u8) + 1 {
return false;
}
}
true
}
}
impl PartialEq for Hand {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for Hand {}
impl PartialOrd for Hand {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Hand {
fn cmp(&self, other: &Self) -> Ordering {
// 首先比较牌型
match self.hand_type.rank().cmp(&other.hand_type.rank()) {
Ordering::Equal => {
// 牌型相同时,比较具体的牌力
self.compare_same_type(other)
}
other => other,
}
}
}
impl Hand {
fn compare_same_type(&self, other: &Self) -> Ordering {
match self.hand_type {
HandType::HighCard | HandType::Flush | HandType::Straight | HandType::StraightFlush => {
// 比较最高牌,然后依次比较
for (self_rank, other_rank) in self.sorted_ranks.iter().zip(other.sorted_ranks.iter()) {
match self_rank.cmp(other_rank) {
Ordering::Equal => continue,
other => return other,
}
}
Ordering::Equal
}
HandType::OnePair => {
// 比较对子的点数
let self_pair_rank = self.get_rank_by_count(2);
let other_pair_rank = other.get_rank_by_count(2);
match self_pair_rank.cmp(&other_pair_rank) {
Ordering::Equal => {
// 对子相同,比较剩余牌
self.compare_kickers(&[self_pair_rank], other, &[other_pair_rank])
}
other => other,
}
}
HandType::TwoPair => {
// 比较两对的点数(从高到低)
let mut self_pairs: Vec<Rank> = self.get_ranks_by_count(2);
let mut other_pairs: Vec<Rank> = other.get_ranks_by_count(2);
self_pairs.sort_by(|a, b| b.cmp(a));
other_pairs.sort_by(|a, b| b.cmp(a));
for (self_rank, other_rank) in self_pairs.iter().zip(other_pairs.iter()) {
match self_rank.cmp(other_rank) {
Ordering::Equal => continue,
other => return other,
}
}
// 两对都相同,比较单牌
self.compare_kickers(&self_pairs, other, &other_pairs)
}
HandType::ThreeOfAKind => {
// 比较三条的点数
let self_three_rank = self.get_rank_by_count(3);
let other_three_rank = other.get_rank_by_count(3);
match self_three_rank.cmp(&other_three_rank) {
Ordering::Equal => {
// 三条相同,比较剩余牌
self.compare_kickers(&[self_three_rank], other, &[other_three_rank])
}
other => other,
}
}
HandType::FullHouse => {
// 比较三条的点数
let self_three_rank = self.get_rank_by_count(3);
let other_three_rank = other.get_rank_by_count(3);
match self_three_rank.cmp(&other_three_rank) {
Ordering::Equal => {
// 三条相同,比较对子
let self_pair_rank = self.get_rank_by_count(2);
let other_pair_rank = other.get_rank_by_count(2);
self_pair_rank.cmp(&other_pair_rank)
}
other => other,
}
}
HandType::FourOfAKind => {
// 比较四条的点数
let self_four_rank = self.get_rank_by_count(4);
let other_four_rank = other.get_rank_by_count(4);
match self_four_rank.cmp(&other_four_rank) {
Ordering::Equal => {
// 四条相同,比较剩余牌
self.compare_kickers(&[self_four_rank], other, &[other_four_rank])
}
other => other,
}
}
}
}
fn get_rank_by_count(&self, count: usize) -> Rank {
self.rank_counts
.iter()
.find(|(_, &c)| c == count)
.map(|(rank, _)| rank.clone())
.unwrap()
}
fn get_ranks_by_count(&self, count: usize) -> Vec<Rank> {
self.rank_counts
.iter()
.filter(|(_, &c)| c == count)
.map(|(rank, _)| rank.clone())
.collect()
}
fn compare_kickers(&self, exclude_ranks: &[Rank], other: &Self, other_exclude_ranks: &[Rank]) -> Ordering {
let mut self_ranks: Vec<Rank> = self.sorted_ranks.clone();
let mut other_ranks: Vec<Rank> = other.sorted_ranks.clone();
// 移除需要排除的点数
self_ranks.retain(|r| !exclude_ranks.contains(r));
other_ranks.retain(|r| !other_exclude_ranks.contains(r));
// 比较剩余牌
for (self_rank, other_rank) in self_ranks.iter().zip(other_ranks.iter()) {
match self_rank.cmp(other_rank) {
Ordering::Equal => continue,
other => return other,
}
}
Ordering::Equal
}
}
测试用例分析
通过查看测试用例,我们可以更好地理解需求:
rust
#[test]
fn test_single_hand_always_wins() {
test(&["4S 5S 7H 8D JC"], &["4S 5S 7H 8D JC"])
}
单个手牌总是获胜。
rust
#[test]
fn test_duplicate_hands_always_tie() {
let input = &["3S 4S 5D 6H JH", "3S 4S 5D 6H JH", "3S 4S 5D 6H JH"];
assert_eq!(&winning_hands(input), input)
}
重复的手牌总是平局。
rust
#[test]
fn test_highest_card_of_all_hands_wins() {
test(
&["4D 5S 6S 8D 3C", "2S 4C 7S 9H 10H", "3S 4S 5D 6H JH"],
&["3S 4S 5D 6H JH"],
)
}
所有手牌中的最高牌获胜(在这种情况下是J)。
rust
#[test]
fn test_a_tie_has_multiple_winners() {
test(
&[
"4D 5S 6S 8D 3C",
"2S 4C 7S 9H 10H",
"3S 4S 5D 6H JH",
"3H 4H 5C 6C JD",
],
&["3S 4S 5D 6H JH", "3H 4H 5C 6C JD"],
)
}
平局时有多个获胜者。
rust
#[test]
fn test_high_card_can_be_low_card_in_an_otherwise_tie() {
// multiple hands with the same high cards, tie compares next highest ranked,
// down to last card
test(&["3S 5H 6S 8D 7H", "2S 5D 6D 8C 7S"], &["3S 5H 6S 8D 7H"])
}
当最高牌相同时,比较次高牌,以此类推。
rust
#[test]
fn test_one_pair_beats_high_card() {
test(&["4S 5H 6C 8D KH", "2S 4H 6S 4D JH"], &["2S 4H 6S 4D JH"])
}
一对牌胜过高牌。
rust
#[test]
fn test_highest_pair_wins() {
test(&["4S 2H 6S 2D JH", "2S 4H 6C 4D JD"], &["2S 4H 6C 4D JD"])
}
比较对子的点数,高对子获胜。
rust
#[test]
fn test_two_pairs_beats_one_pair() {
test(&["2S 8H 6S 8D JH", "4S 5H 4C 8C 5C"], &["4S 5H 4C 8C 5C"])
}
两对胜过一对。
rust
#[test]
fn test_two_pair_ranks() {
// both hands have two pairs, highest ranked pair wins
test(&["2S 8H 2D 8D 3H", "4S 5H 4C 8S 5D"], &["2S 8H 2D 8D 3H"])
}
两对牌比较时,首先比较最高对子。
rust
#[test]
fn test_two_pairs_second_pair_cascade() {
// both hands have two pairs, with the same highest ranked pair,
// tie goes to low pair
test(&["2S QS 2C QD JH", "JD QH JS 8D QC"], &["JD QH JS 8D QC"])
}
最高对子相同时,比较次高对子。
rust
#[test]
fn test_two_pairs_last_card_cascade() {
// both hands have two identically ranked pairs,
// tie goes to remaining card (kicker)
test(&["JD QH JS 8D QC", "JS QS JC 2D QD"], &["JD QH JS 8D QC"])
}
两对完全相同时,比较剩余的牌(kicker)。
rust
#[test]
fn test_three_of_a_kind_beats_two_pair() {
test(&["2S 8H 2H 8D JH", "4S 5H 4C 8S 4H"], &["4S 5H 4C 8S 4H"])
}
三条胜过两对。
rust
#[test]
fn test_three_of_a_kind_ranks() {
//both hands have three of a kind, tie goes to highest ranked triplet
test(&["2S 2H 2C 8D JH", "4S AH AS 8C AD"], &["4S AH AS 8C AD"])
}
三条比较时,点数高的三条获胜。
rust
#[test]
fn test_three_of_a_kind_cascade_ranks() {
// with multiple decks, two players can have same three of a kind,
// ties go to highest remaining cards
test(&["4S AH AS 7C AD", "4S AH AS 8C AD"], &["4S AH AS 8C AD"])
}
三条相同时,比较剩余牌的点数。
rust
#[test]
fn test_straight_beats_three_of_a_kind() {
test(&["4S 5H 4C 8D 4H", "3S 4D 2S 6D 5C"], &["3S 4D 2S 6D 5C"])
}
顺子胜过三条。
rust
#[test]
fn test_aces_can_end_a_straight_high() {
// aces can end a straight (10 J Q K A)
test(&["4S 5H 4C 8D 4H", "10D JH QS KD AC"], &["10D JH QS KD AC"])
}
A可以作为顺子的最高牌(10 J Q K A)。
rust
#[test]
fn test_aces_can_end_a_straight_low() {
// aces can start a straight (A 2 3 4 5)
test(&["4S 5H 4C 8D 4H", "4D AH 3S 2D 5C"], &["4D AH 3S 2D 5C"])
}
A可以作为顺子的最低牌(A 2 3 4 5)。
rust
#[test]
fn test_straight_cascade() {
// both hands with a straight, tie goes to highest ranked card
test(&["4S 6C 7S 8D 5H", "5S 7H 8S 9D 6H"], &["5S 7H 8S 9D 6H"])
}
顺子比较时,点数高的顺子获胜。
rust
#[test]
fn test_straight_scoring() {
// even though an ace is usually high, a 5-high straight is the lowest-scoring straight
test(&["2H 3C 4D 5D 6H", "4S AH 3S 2D 5H"], &["2H 3C 4D 5D 6H"])
}
尽管A通常作为高牌,但5-high顺子(A 2 3 4 5)是最低的顺子。
rust
#[test]
fn test_flush_beats_a_straight() {
test(&["4C 6H 7D 8D 5H", "2S 4S 5S 6S 7S"], &["2S 4S 5S 6S 7S"])
}
同花胜过顺子。
rust
#[test]
fn test_flush_cascade() {
// both hands have a flush, tie goes to high card, down to the last one if necessary
test(&["4H 7H 8H 9H 6H", "2S 4S 5S 6S 7S"], &["4H 7H 8H 9H 6H"])
}
同花比较时,比较最高牌,然后依次比较。
rust
#[test]
fn test_full_house_beats_a_flush() {
test(&["3H 6H 7H 8H 5H", "4S 5C 4C 5D 4H"], &["4S 5C 4C 5D 4H"])
}
葫芦胜过同花。
rust
#[test]
fn test_full_house_ranks() {
// both hands have a full house, tie goes to highest-ranked triplet
test(&["4H 4S 4D 9S 9D", "5H 5S 5D 8S 8D"], &["5H 5S 5D 8S 8D"])
}
葫芦比较时,三条点数高的获胜。
rust
#[test]
fn test_full_house_cascade() {
// with multiple decks, both hands have a full house with the same triplet, tie goes to the pair
test(&["5H 5S 5D 9S 9D", "5H 5S 5D 8S 8D"], &["5H 5S 5D 9S 9D"])
}
三条相同时,比较对子点数。
rust
#[test]
fn test_four_of_a_kind_beats_full_house() {
test(&["4S 5H 4D 5D 4H", "3S 3H 2S 3D 3C"], &["3S 3H 2S 3D 3C"])
}
四条胜过葫芦。
rust
#[test]
fn test_four_of_a_kind_ranks() {
// both hands have four of a kind, tie goes to high quad
test(&["2S 2H 2C 8D 2D", "4S 5H 5S 5D 5C"], &["4S 5H 5S 5D 5C"])
}
四条比较时,点数高的四条获胜。
rust
#[test]
fn test_four_of_a_kind_cascade() {
// with multiple decks, both hands with identical four of a kind, tie determined by kicker
test(&["3S 3H 2S 3D 3C", "3S 3H 4S 3D 3C"], &["3S 3H 4S 3D 3C"])
}
四条相同时,比较剩余牌(kicker)。
rust
#[test]
fn test_straight_flush_beats_four_of_a_kind() {
test(&["4S 5H 5S 5D 5C", "7S 8S 9S 6S 10S"], &["7S 8S 9S 6S 10S"])
}
同花顺胜过四条。
rust
#[test]
fn test_straight_flush_ranks() {
// both hands have straight flush, tie goes to highest-ranked card
test(&["4H 6H 7H 8H 5H", "5S 7S 8S 9S 6S"], &["5S 7S 8S 9S 6S"])
}
同花顺比较时,点数高的获胜。
性能优化版本
考虑性能的优化实现:
rust
use std::cmp::Ordering;
use std::collections::HashMap;
/// Given a list of poker hands, return a list of those hands which win.
///
/// Note the type signature: this function should return _the same_ reference to
/// the winning hand(s) as were passed in, not reconstructed strings which happen to be equal.
pub fn winning_hands<'a>(hands: &[&'a str]) -> Vec<&'a str> {
if hands.is_empty() {
return vec![];
}
// 预分配结果向量以避免多次重新分配
let mut parsed_hands: Vec<(Hand, &str)> = Vec::with_capacity(hands.len());
// 将字符串手牌转换为Hand结构体
for &hand_str in hands {
parsed_hands.push((Hand::from_str(hand_str), hand_str));
}
// 按照牌力排序(降序)
parsed_hands.sort_unstable_by(|a, b| b.0.cmp(&a.0));
// 找到所有获胜的手牌(与最高牌力相同的手牌)
let winning_hand = &parsed_hands[0].0;
let mut winners = Vec::with_capacity(parsed_hands.len());
for (hand, hand_str) in parsed_hands {
if hand.cmp(winning_hand) == Ordering::Equal {
winners.push(hand_str);
} else {
break; // 由于已排序,后续手牌都不可能是获胜者
}
}
winners
}
// 使用数组代替HashMap以提高性能
#[derive(Debug, Clone, PartialEq, Eq)]
struct Card {
rank: Rank,
suit: Suit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
enum Rank {
Two = 2,
Three = 3,
Four = 4,
Five = 5,
Six = 6,
Seven = 7,
Eight = 8,
Nine = 9,
Ten = 10,
Jack = 11,
Queen = 12,
King = 13,
Ace = 14,
}
impl Rank {
fn from_char(c: char) -> Option<Rank> {
match c {
'2' => Some(Rank::Two),
'3' => Some(Rank::Three),
'4' => Some(Rank::Four),
'5' => Some(Rank::Five),
'6' => Some(Rank::Six),
'7' => Some(Rank::Seven),
'8' => Some(Rank::Eight),
'9' => Some(Rank::Nine),
'T' | '0' => Some(Rank::Ten),
'J' => Some(Rank::Jack),
'Q' => Some(Rank::Queen),
'K' => Some(Rank::King),
'A' => Some(Rank::Ace),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
enum Suit {
Clubs = 0,
Diamonds = 1,
Hearts = 2,
Spades = 3,
}
impl Suit {
fn from_char(c: char) -> Option<Suit> {
match c {
'C' => Some(Suit::Clubs),
'D' => Some(Suit::Diamonds),
'H' => Some(Suit::Hearts),
'S' => Some(Suit::Spades),
_ => None,
}
}
}
impl Card {
fn from_str(s: &str) -> Card {
let chars: Vec<char> = s.chars().collect();
if chars.is_empty() {
panic!("Invalid card string: {}", s);
}
let (rank_char, suit_char) = if chars.len() >= 3 && chars[0] == '1' && chars[1] == '0' {
// 处理"10"的情况
if chars.len() >= 3 {
('0', chars[2])
} else {
panic!("Invalid card string: {}", s);
}
} else if chars.len() >= 2 {
(chars[0], chars[chars.len() - 1])
} else {
panic!("Invalid card string: {}", s);
};
let rank = Rank::from_char(rank_char)
.unwrap_or_else(|| panic!("Invalid rank in card string: {}", s));
let suit = Suit::from_char(suit_char)
.unwrap_or_else(|| panic!("Invalid suit in card string: {}", s));
Card { rank, suit }
}
}
#[derive(Debug, Clone)]
struct Hand {
cards: [Card; 5],
hand_type: HandType,
rank_counts: [u8; 15], // 索引0未使用,索引1未使用,索引2-14对应Rank::Two-Rank::Ace
sorted_ranks: [Rank; 5],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
enum HandType {
HighCard = 0,
OnePair = 1,
TwoPair = 2,
ThreeOfAKind = 3,
Straight = 4,
Flush = 5,
FullHouse = 6,
FourOfAKind = 7,
StraightFlush = 8,
}
impl HandType {
fn rank(&self) -> u8 {
*self as u8
}
}
impl Hand {
fn from_str(hand_str: &str) -> Hand {
let card_strs: Vec<&str> = hand_str.split_whitespace().collect();
if card_strs.len() != 5 {
panic!("Invalid hand: must contain exactly 5 cards");
}
let cards: [Card; 5] = [
Card::from_str(card_strs[0]),
Card::from_str(card_strs[1]),
Card::from_str(card_strs[2]),
Card::from_str(card_strs[3]),
Card::from_str(card_strs[4]),
];
let mut rank_counts = [0u8; 15];
for card in &cards {
rank_counts[card.rank as usize] += 1;
}
let hand_type = Self::determine_hand_type(&cards, &rank_counts);
let mut sorted_ranks: [Rank; 5] = [
cards[0].rank,
cards[1].rank,
cards[2].rank,
cards[3].rank,
cards[4].rank,
];
sorted_ranks.sort_by(|a, b| b.cmp(a)); // 降序排列
Hand {
cards,
hand_type,
rank_counts,
sorted_ranks,
}
}
fn determine_hand_type(cards: &[Card; 5], rank_counts: &[u8; 15]) -> HandType {
let is_flush = cards.iter().all(|card| card.suit == cards[0].suit);
let mut ranks: [Rank; 5] = [
cards[0].rank,
cards[1].rank,
cards[2].rank,
cards[3].rank,
cards[4].rank,
];
ranks.sort();
let is_straight = Self::is_straight(&ranks);
// 特殊情况:A,2,3,4,5 是顺子
let is_low_straight = ranks == [Rank::Two, Rank::Three, Rank::Four, Rank::Five, Rank::Ace];
if is_flush && (is_straight || is_low_straight) {
return HandType::StraightFlush;
}
// 统计不同点数的牌的数量
let mut count_counts = [0u8; 5]; // 索引表示相同点数的牌的数量,值表示有几种这样的牌
for &count in rank_counts.iter() {
if count > 0 {
count_counts[count as usize] += 1;
}
}
if count_counts[4] == 1 {
HandType::FourOfAKind
} else if count_counts[3] == 1 && count_counts[2] == 1 {
HandType::FullHouse
} else if is_flush {
HandType::Flush
} else if is_straight || is_low_straight {
HandType::Straight
} else if count_counts[3] == 1 {
HandType::ThreeOfAKind
} else if count_counts[2] == 2 {
HandType::TwoPair
} else if count_counts[2] == 1 {
HandType::OnePair
} else {
HandType::HighCard
}
}
fn is_straight(ranks: &[Rank; 5]) -> bool {
for i in 0..4 {
if (ranks[i + 1] as u8) != (ranks[i] as u8) + 1 {
return false;
}
}
true
}
}
impl PartialEq for Hand {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for Hand {}
impl PartialOrd for Hand {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Hand {
fn cmp(&self, other: &Self) -> Ordering {
// 首先比较牌型
match self.hand_type.rank().cmp(&other.hand_type.rank()) {
Ordering::Equal => {
// 牌型相同时,比较具体的牌力
self.compare_same_type(other)
}
other => other,
}
}
}
impl Hand {
fn compare_same_type(&self, other: &Self) -> Ordering {
match self.hand_type {
HandType::HighCard | HandType::Flush | HandType::Straight | HandType::StraightFlush => {
// 比较最高牌,然后依次比较
for i in 0..5 {
match self.sorted_ranks[i].cmp(&other.sorted_ranks[i]) {
Ordering::Equal => continue,
other => return other,
}
}
Ordering::Equal
}
HandType::OnePair => {
// 比较对子的点数
let self_pair_rank = self.get_rank_by_count(2);
let other_pair_rank = other.get_rank_by_count(2);
match self_pair_rank.cmp(&other_pair_rank) {
Ordering::Equal => {
// 对子相同,比较剩余牌
self.compare_kickers(&[self_pair_rank], other, &[other_pair_rank])
}
other => other,
}
}
HandType::TwoPair => {
// 比较两对的点数(从高到低)
let mut self_pairs: Vec<Rank> = self.get_ranks_by_count(2);
let mut other_pairs: Vec<Rank> = other.get_ranks_by_count(2);
self_pairs.sort_by(|a, b| b.cmp(a));
other_pairs.sort_by(|a, b| b.cmp(a));
for i in 0..2 {
match self_pairs[i].cmp(&other_pairs[i]) {
Ordering::Equal => continue,
other => return other,
}
}
// 两对都相同,比较单牌
self.compare_kickers(&self_pairs, other, &other_pairs)
}
HandType::ThreeOfAKind => {
// 比较三条的点数
let self_three_rank = self.get_rank_by_count(3);
let other_three_rank = other.get_rank_by_count(3);
match self_three_rank.cmp(&other_three_rank) {
Ordering::Equal => {
// 三条相同,比较剩余牌
self.compare_kickers(&[self_three_rank], other, &[other_three_rank])
}
other => other,
}
}
HandType::FullHouse => {
// 比较三条的点数
let self_three_rank = self.get_rank_by_count(3);
let other_three_rank = other.get_rank_by_count(3);
match self_three_rank.cmp(&other_three_rank) {
Ordering::Equal => {
// 三条相同,比较对子
let self_pair_rank = self.get_rank_by_count(2);
let other_pair_rank = other.get_rank_by_count(2);
self_pair_rank.cmp(&other_pair_rank)
}
other => other,
}
}
HandType::FourOfAKind => {
// 比较四条的点数
let self_four_rank = self.get_rank_by_count(4);
let other_four_rank = other.get_rank_by_count(4);
match self_four_rank.cmp(&other_four_rank) {
Ordering::Equal => {
// 四条相同,比较剩余牌
self.compare_kickers(&[self_four_rank], other, &[other_four_rank])
}
other => other,
}
}
}
}
fn get_rank_by_count(&self, count: u8) -> Rank {
for (rank_index, &rank_count) in self.rank_counts.iter().enumerate() {
if rank_count == count {
// 安全地将索引转换为Rank
return unsafe { std::mem::transmute(rank_index as u8) };
}
}
panic!("No rank with count {}", count);
}
fn get_ranks_by_count(&self, count: u8) -> Vec<Rank> {
let mut ranks = Vec::new();
for (rank_index, &rank_count) in self.rank_counts.iter().enumerate() {
if rank_count == count {
// 安全地将索引转换为Rank
ranks.push(unsafe { std::mem::transmute(rank_index as u8) });
}
}
ranks
}
fn compare_kickers(&self, exclude_ranks: &[Rank], other: &Self, other_exclude_ranks: &[Rank]) -> Ordering {
let mut self_ranks = self.sorted_ranks;
let mut other_ranks = other.sorted_ranks;
// 移除需要排除的点数
self_ranks.retain(|r| !exclude_ranks.contains(r));
other_ranks.retain(|r| !other_exclude_ranks.contains(r));
// 比较剩余牌
for i in 0..self_ranks.len() {
match self_ranks[i].cmp(&other_ranks[i]) {
Ordering::Equal => continue,
other => return other,
}
}
Ordering::Equal
}
}
错误处理和边界情况
考虑更多边界情况的实现:
rust
use std::cmp::Ordering;
use std::collections::HashMap;
/// Given a list of poker hands, return a list of those hands which win.
///
/// Note the type signature: this function should return _the same_ reference to
/// the winning hand(s) as were passed in, not reconstructed strings which happen to be equal.
pub fn winning_hands<'a>(hands: &[&'a str]) -> Vec<&'a str> {
// 处理空输入
if hands.is_empty() {
return vec![];
}
// 将字符串手牌转换为Hand结构体
let mut parsed_hands: Vec<Result<(Hand, &str), String>> = hands
.iter()
.map(|&hand_str| {
Hand::from_str(hand_str)
.map(|hand| (hand, hand_str))
.map_err(|e| format!("Invalid hand '{}': {}", hand_str, e))
})
.collect();
// 检查是否有无效手牌
if let Some(Err(error)) = parsed_hands.iter().find(|result| result.is_err()) {
panic!("{}", error);
}
// 解包有效手牌
let mut valid_hands: Vec<(Hand, &str)> = parsed_hands
.into_iter()
.map(|result| result.unwrap())
.collect();
// 按照牌力排序(降序)
valid_hands.sort_unstable_by(|a, b| b.0.cmp(&a.0));
// 找到所有获胜的手牌(与最高牌力相同的手牌)
let winning_hand = &valid_hands[0].0;
valid_hands
.into_iter()
.take_while(|(hand, _)| hand.cmp(winning_hand) == Ordering::Equal)
.map(|(_, hand_str)| hand_str)
.collect()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Card {
rank: Rank,
suit: Suit,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Rank {
Two = 2,
Three = 3,
Four = 4,
Five = 5,
Six = 6,
Seven = 7,
Eight = 8,
Nine = 9,
Ten = 10,
Jack = 11,
Queen = 12,
King = 13,
Ace = 14,
}
impl Rank {
fn from_char(c: char) -> Result<Rank, String> {
match c {
'2' => Ok(Rank::Two),
'3' => Ok(Rank::Three),
'4' => Ok(Rank::Four),
'5' => Ok(Rank::Five),
'6' => Ok(Rank::Six),
'7' => Ok(Rank::Seven),
'8' => Ok(Rank::Eight),
'9' => Ok(Rank::Nine),
'T' | '0' => Ok(Rank::Ten),
'J' => Ok(Rank::Jack),
'Q' => Ok(Rank::Queen),
'K' => Ok(Rank::King),
'A' => Ok(Rank::Ace),
_ => Err(format!("Invalid rank character: {}", c)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Suit {
Clubs,
Diamonds,
Hearts,
Spades,
}
impl Suit {
fn from_char(c: char) -> Result<Suit, String> {
match c {
'C' => Ok(Suit::Clubs),
'D' => Ok(Suit::Diamonds),
'H' => Ok(Suit::Hearts),
'S' => Ok(Suit::Spades),
_ => Err(format!("Invalid suit character: {}", c)),
}
}
}
impl Card {
fn from_str(s: &str) -> Result<Card, String> {
let chars: Vec<char> = s.chars().collect();
if chars.is_empty() {
return Err("Empty card string".to_string());
}
let (rank_char, suit_char) = if chars.len() >= 3 && chars[0] == '1' && chars[1] == '0' {
// 处理"10"的情况
if chars.len() >= 3 {
('0', chars[2])
} else {
return Err("Invalid card string for 10".to_string());
}
} else if chars.len() >= 2 {
(chars[0], chars[chars.len() - 1])
} else {
return Err("Card string too short".to_string());
};
let rank = Rank::from_char(rank_char)?;
let suit = Suit::from_char(suit_char)?;
Ok(Card { rank, suit })
}
}
#[derive(Debug, Clone)]
pub struct Hand {
cards: Vec<Card>,
hand_type: HandType,
rank_counts: HashMap<Rank, usize>,
sorted_ranks: Vec<Rank>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HandType {
HighCard,
OnePair,
TwoPair,
ThreeOfAKind,
Straight,
Flush,
FullHouse,
FourOfAKind,
StraightFlush,
}
impl HandType {
fn rank(&self) -> u8 {
match self {
HandType::HighCard => 0,
HandType::OnePair => 1,
HandType::TwoPair => 2,
HandType::ThreeOfAKind => 3,
HandType::Straight => 4,
HandType::Flush => 5,
HandType::FullHouse => 6,
HandType::FourOfAKind => 7,
HandType::StraightFlush => 8,
}
}
}
#[derive(Debug)]
pub enum PokerError {
InvalidHand(String),
InvalidCard(String),
}
impl std::fmt::Display for PokerError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
PokerError::InvalidHand(msg) => write!(f, "Invalid hand: {}", msg),
PokerError::InvalidCard(msg) => write!(f, "Invalid card: {}", msg),
}
}
}
impl std::error::Error for PokerError {}
impl Hand {
fn from_str(hand_str: &str) -> Result<Hand, PokerError> {
let card_strs: Vec<&str> = hand_str.split_whitespace().collect();
if card_strs.len() != 5 {
return Err(PokerError::InvalidHand(
"must contain exactly 5 cards".to_string(),
));
}
let mut cards = Vec::with_capacity(5);
for card_str in card_strs {
cards.push(Card::from_str(card_str).map_err(PokerError::InvalidCard)?);
}
let mut rank_counts = HashMap::new();
for card in &cards {
*rank_counts.entry(card.rank.clone()).or_insert(0) += 1;
}
let hand_type = Self::determine_hand_type(&cards, &rank_counts);
let mut sorted_ranks: Vec<Rank> = cards.iter().map(|c| c.rank.clone()).collect();
sorted_ranks.sort_by(|a, b| b.cmp(a)); // 降序排列
Ok(Hand {
cards,
hand_type,
rank_counts,
sorted_ranks,
})
}
fn determine_hand_type(cards: &[Card], rank_counts: &HashMap<Rank, usize>) -> HandType {
let is_flush = cards.iter().all(|card| card.suit == cards[0].suit);
let mut ranks: Vec<Rank> = cards.iter().map(|c| c.rank.clone()).collect();
ranks.sort();
let is_straight = Self::is_straight(&ranks);
// 特殊情况:A,2,3,4,5 是顺子
let is_low_straight = ranks == vec![Rank::Two, Rank::Three, Rank::Four, Rank::Five, Rank::Ace];
if is_flush && (is_straight || is_low_straight) {
return HandType::StraightFlush;
}
let counts: Vec<usize> = rank_counts.values().cloned().collect();
let mut count_counts = HashMap::new();
for &count in &counts {
*count_counts.entry(count).or_insert(0) += 1;
}
if count_counts.get(&4).unwrap_or(&0) == &1 {
HandType::FourOfAKind
} else if count_counts.get(&3).unwrap_or(&0) == &1 && count_counts.get(&2).unwrap_or(&0) == &1 {
HandType::FullHouse
} else if is_flush {
HandType::Flush
} else if is_straight || is_low_straight {
HandType::Straight
} else if count_counts.get(&3).unwrap_or(&0) == &1 {
HandType::ThreeOfAKind
} else if count_counts.get(&2).unwrap_or(&0) == &2 {
HandType::TwoPair
} else if count_counts.get(&2).unwrap_or(&0) == &1 {
HandType::OnePair
} else {
HandType::HighCard
}
}
fn is_straight(ranks: &[Rank]) -> bool {
if ranks.len() != 5 {
return false;
}
for i in 0..4 {
if (ranks[i + 1] as u8) != (ranks[i] as u8) + 1 {
return false;
}
}
true
}
}
impl PartialEq for Hand {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for Hand {}
impl PartialOrd for Hand {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Hand {
fn cmp(&self, other: &Self) -> Ordering {
// 首先比较牌型
match self.hand_type.rank().cmp(&other.hand_type.rank()) {
Ordering::Equal => {
// 牌型相同时,比较具体的牌力
self.compare_same_type(other)
}
other => other,
}
}
}
impl Hand {
fn compare_same_type(&self, other: &Self) -> Ordering {
match self.hand_type {
HandType::HighCard | HandType::Flush | HandType::Straight | HandType::StraightFlush => {
// 比较最高牌,然后依次比较
for (self_rank, other_rank) in self.sorted_ranks.iter().zip(other.sorted_ranks.iter()) {
match self_rank.cmp(other_rank) {
Ordering::Equal => continue,
other => return other,
}
}
Ordering::Equal
}
HandType::OnePair => {
// 比较对子的点数
let self_pair_rank = self.get_rank_by_count(2);
let other_pair_rank = other.get_rank_by_count(2);
match self_pair_rank.cmp(&other_pair_rank) {
Ordering::Equal => {
// 对子相同,比较剩余牌
self.compare_kickers(&[self_pair_rank], other, &[other_pair_rank])
}
other => other,
}
}
HandType::TwoPair => {
// 比较两对的点数(从高到低)
let mut self_pairs: Vec<Rank> = self.get_ranks_by_count(2);
let mut other_pairs: Vec<Rank> = other.get_ranks_by_count(2);
self_pairs.sort_by(|a, b| b.cmp(a));
other_pairs.sort_by(|a, b| b.cmp(a));
for (self_rank, other_rank) in self_pairs.iter().zip(other_pairs.iter()) {
match self_rank.cmp(other_rank) {
Ordering::Equal => continue,
other => return other,
}
}
// 两对都相同,比较单牌
self.compare_kickers(&self_pairs, other, &other_pairs)
}
HandType::ThreeOfAKind => {
// 比较三条的点数
let self_three_rank = self.get_rank_by_count(3);
let other_three_rank = other.get_rank_by_count(3);
match self_three_rank.cmp(&other_three_rank) {
Ordering::Equal => {
// 三条相同,比较剩余牌
self.compare_kickers(&[self_three_rank], other, &[other_three_rank])
}
other => other,
}
}
HandType::FullHouse => {
// 比较三条的点数
let self_three_rank = self.get_rank_by_count(3);
let other_three_rank = other.get_rank_by_count(3);
match self_three_rank.cmp(&other_three_rank) {
Ordering::Equal => {
// 三条相同,比较对子
let self_pair_rank = self.get_rank_by_count(2);
let other_pair_rank = other.get_rank_by_count(2);
self_pair_rank.cmp(&other_pair_rank)
}
other => other,
}
}
HandType::FourOfAKind => {
// 比较四条的点数
let self_four_rank = self.get_rank_by_count(4);
let other_four_rank = other.get_rank_by_count(4);
match self_four_rank.cmp(&other_four_rank) {
Ordering::Equal => {
// 四条相同,比较剩余牌
self.compare_kickers(&[self_four_rank], other, &[other_four_rank])
}
other => other,
}
}
}
}
fn get_rank_by_count(&self, count: usize) -> Rank {
self.rank_counts
.iter()
.find(|(_, &c)| c == count)
.map(|(rank, _)| rank.clone())
.unwrap()
}
fn get_ranks_by_count(&self, count: usize) -> Vec<Rank> {
self.rank_counts
.iter()
.filter(|(_, &c)| c == count)
.map(|(rank, _)| rank.clone())
.collect()
}
fn compare_kickers(&self, exclude_ranks: &[Rank], other: &Self, other_exclude_ranks: &[Rank]) -> Ordering {
let mut self_ranks: Vec<Rank> = self.sorted_ranks.clone();
let mut other_ranks: Vec<Rank> = other.sorted_ranks.clone();
// 移除需要排除的点数
self_ranks.retain(|r| !exclude_ranks.contains(r));
other_ranks.retain(|r| !other_exclude_ranks.contains(r));
// 比较剩余牌
for (self_rank, other_rank) in self_ranks.iter().zip(other_ranks.iter()) {
match self_rank.cmp(other_rank) {
Ordering::Equal => continue,
other => return other,
}
}
Ordering::Equal
}
}
扩展功能
基于基础实现,我们可以添加更多功能:
rust
use std::cmp::Ordering;
use std::collections::HashMap;
/// Given a list of poker hands, return a list of those hands which win.
///
/// Note the type signature: this function should return _the same_ reference to
/// the winning hand(s) as were passed in, not reconstructed strings which happen to be equal.
pub fn winning_hands<'a>(hands: &[&'a str]) -> Vec<&'a str> {
if hands.is_empty() {
return vec![];
}
let mut parsed_hands: Vec<(Hand, &str)> = hands
.iter()
.map(|&hand_str| (Hand::from_str(hand_str), hand_str))
.collect();
parsed_hands.sort_unstable_by(|a, b| b.0.cmp(&a.0));
let winning_hand = &parsed_hands[0].0;
parsed_hands
.into_iter()
.take_while(|(hand, _)| hand.cmp(winning_hand) == Ordering::Equal)
.map(|(_, hand_str)| hand_str)
.collect()
}
pub struct PokerGame {
hands: Vec<Hand>,
original_strings: Vec<String>,
}
impl PokerGame {
pub fn new() -> Self {
PokerGame {
hands: Vec::new(),
original_strings: Vec::new(),
}
}
pub fn add_hand(&mut self, hand_str: &str) -> Result<(), String> {
let hand = Hand::from_str(hand_str)?;
self.hands.push(hand);
self.original_strings.push(hand_str.to_string());
Ok(())
}
pub fn get_winning_hands(&self) -> Vec<&str> {
if self.hands.is_empty() {
return vec![];
}
let mut indices: Vec<usize> = (0..self.hands.len()).collect();
indices.sort_by(|&a, &b| self.hands[b].cmp(&self.hands[a]));
let winning_hand = &self.hands[indices[0]];
indices
.into_iter()
.take_while(|&i| self.hands[i].cmp(winning_hand) == Ordering::Equal)
.map(|i| self.original_strings[i].as_str())
.collect()
}
pub fn get_hand_type(&self, index: usize) -> Option<&HandType> {
self.hands.get(index).map(|hand| &hand.hand_type)
}
pub fn get_hand_ranking(&self, hand_str: &str) -> Option<usize> {
let mut indices: Vec<usize> = (0..self.hands.len()).collect();
indices.sort_by(|&a, &b| self.hands[b].cmp(&self.hands[a]));
indices.iter().position(|&i| self.original_strings[i] == hand_str)
}
pub fn compare_hands(&self, index1: usize, index2: usize) -> Option<Ordering> {
match (self.hands.get(index1), self.hands.get(index2)) {
(Some(hand1), Some(hand2)) => Some(hand1.cmp(hand2)),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Card {
rank: Rank,
suit: Suit,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Rank {
Two = 2,
Three = 3,
Four = 4,
Five = 5,
Six = 6,
Seven = 7,
Eight = 8,
Nine = 9,
Ten = 10,
Jack = 11,
Queen = 12,
King = 13,
Ace = 14,
}
impl Rank {
fn from_char(c: char) -> Result<Rank, String> {
match c {
'2' => Ok(Rank::Two),
'3' => Ok(Rank::Three),
'4' => Ok(Rank::Four),
'5' => Ok(Rank::Five),
'6' => Ok(Rank::Six),
'7' => Ok(Rank::Seven),
'8' => Ok(Rank::Eight),
'9' => Ok(Rank::Nine),
'T' | '0' => Ok(Rank::Ten),
'J' => Ok(Rank::Jack),
'Q' => Ok(Rank::Queen),
'K' => Ok(Rank::King),
'A' => Ok(Rank::Ace),
_ => Err(format!("Invalid rank character: {}", c)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Suit {
Clubs,
Diamonds,
Hearts,
Spades,
}
impl Suit {
fn from_char(c: char) -> Result<Suit, String> {
match c {
'C' => Ok(Suit::Clubs),
'D' => Ok(Suit::Diamonds),
'H' => Ok(Suit::Hearts),
'S' => Ok(Suit::Spades),
_ => Err(format!("Invalid suit character: {}", c)),
}
}
}
impl Card {
fn from_str(s: &str) -> Result<Card, String> {
let chars: Vec<char> = s.chars().collect();
if chars.is_empty() {
return Err("Empty card string".to_string());
}
let (rank_char, suit_char) = if chars.len() >= 3 && chars[0] == '1' && chars[1] == '0' {
// 处理"10"的情况
if chars.len() >= 3 {
('0', chars[2])
} else {
return Err("Invalid card string for 10".to_string());
}
} else if chars.len() >= 2 {
(chars[0], chars[chars.len() - 1])
} else {
return Err("Card string too short".to_string());
};
let rank = Rank::from_char(rank_char)?;
let suit = Suit::from_char(suit_char)?;
Ok(Card { rank, suit })
}
}
#[derive(Debug, Clone)]
pub struct Hand {
cards: Vec<Card>,
pub hand_type: HandType,
rank_counts: HashMap<Rank, usize>,
sorted_ranks: Vec<Rank>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HandType {
HighCard,
OnePair,
TwoPair,
ThreeOfAKind,
Straight,
Flush,
FullHouse,
FourOfAKind,
StraightFlush,
}
impl HandType {
pub fn name(&self) -> &'static str {
match self {
HandType::HighCard => "High Card",
HandType::OnePair => "One Pair",
HandType::TwoPair => "Two Pair",
HandType::ThreeOfAKind => "Three of a Kind",
HandType::Straight => "Straight",
HandType::Flush => "Flush",
HandType::FullHouse => "Full House",
HandType::FourOfAKind => "Four of a Kind",
HandType::StraightFlush => "Straight Flush",
}
}
fn rank(&self) -> u8 {
match self {
HandType::HighCard => 0,
HandType::OnePair => 1,
HandType::TwoPair => 2,
HandType::ThreeOfAKind => 3,
HandType::Straight => 4,
HandType::Flush => 5,
HandType::FullHouse => 6,
HandType::FourOfAKind => 7,
HandType::StraightFlush => 8,
}
}
}
impl Hand {
fn from_str(hand_str: &str) -> Result<Hand, String> {
let card_strs: Vec<&str> = hand_str.split_whitespace().collect();
if card_strs.len() != 5 {
return Err("must contain exactly 5 cards".to_string());
}
let mut cards = Vec::with_capacity(5);
for card_str in card_strs {
cards.push(Card::from_str(card_str)?);
}
let mut rank_counts = HashMap::new();
for card in &cards {
*rank_counts.entry(card.rank.clone()).or_insert(0) += 1;
}
let hand_type = Self::determine_hand_type(&cards, &rank_counts);
let mut sorted_ranks: Vec<Rank> = cards.iter().map(|c| c.rank.clone()).collect();
sorted_ranks.sort_by(|a, b| b.cmp(a)); // 降序排列
Ok(Hand {
cards,
hand_type,
rank_counts,
sorted_ranks,
})
}
fn determine_hand_type(cards: &[Card], rank_counts: &HashMap<Rank, usize>) -> HandType {
let is_flush = cards.iter().all(|card| card.suit == cards[0].suit);
let mut ranks: Vec<Rank> = cards.iter().map(|c| c.rank.clone()).collect();
ranks.sort();
let is_straight = Self::is_straight(&ranks);
// 特殊情况:A,2,3,4,5 是顺子
let is_low_straight = ranks == vec![Rank::Two, Rank::Three, Rank::Four, Rank::Five, Rank::Ace];
if is_flush && (is_straight || is_low_straight) {
return HandType::StraightFlush;
}
let counts: Vec<usize> = rank_counts.values().cloned().collect();
let mut count_counts = HashMap::new();
for &count in &counts {
*count_counts.entry(count).or_insert(0) += 1;
}
if count_counts.get(&4).unwrap_or(&0) == &1 {
HandType::FourOfAKind
} else if count_counts.get(&3).unwrap_or(&0) == &1 && count_counts.get(&2).unwrap_or(&0) == &1 {
HandType::FullHouse
} else if is_flush {
HandType::Flush
} else if is_straight || is_low_straight {
HandType::Straight
} else if count_counts.get(&3).unwrap_or(&0) == &1 {
HandType::ThreeOfAKind
} else if count_counts.get(&2).unwrap_or(&0) == &2 {
HandType::TwoPair
} else if count_counts.get(&2).unwrap_or(&0) == &1 {
HandType::OnePair
} else {
HandType::HighCard
}
}
fn is_straight(ranks: &[Rank]) -> bool {
if ranks.len() != 5 {
return false;
}
for i in 0..4 {
if (ranks[i + 1] as u8) != (ranks[i] as u8) + 1 {
return false;
}
}
true
}
}
impl PartialEq for Hand {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for Hand {}
impl PartialOrd for Hand {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Hand {
fn cmp(&self, other: &Self) -> Ordering {
match self.hand_type.rank().cmp(&other.hand_type.rank()) {
Ordering::Equal => {
self.compare_same_type(other)
}
other => other,
}
}
}
impl Hand {
fn compare_same_type(&self, other: &Self) -> Ordering {
match self.hand_type {
HandType::HighCard | HandType::Flush | HandType::Straight | HandType::StraightFlush => {
for (self_rank, other_rank) in self.sorted_ranks.iter().zip(other.sorted_ranks.iter()) {
match self_rank.cmp(other_rank) {
Ordering::Equal => continue,
other => return other,
}
}
Ordering::Equal
}
HandType::OnePair => {
let self_pair_rank = self.get_rank_by_count(2);
let other_pair_rank = other.get_rank_by_count(2);
match self_pair_rank.cmp(&other_pair_rank) {
Ordering::Equal => {
self.compare_kickers(&[self_pair_rank], other, &[other_pair_rank])
}
other => other,
}
}
HandType::TwoPair => {
let mut self_pairs: Vec<Rank> = self.get_ranks_by_count(2);
let mut other_pairs: Vec<Rank> = other.get_ranks_by_count(2);
self_pairs.sort_by(|a, b| b.cmp(a));
other_pairs.sort_by(|a, b| b.cmp(a));
for (self_rank, other_rank) in self_pairs.iter().zip(other_pairs.iter()) {
match self_rank.cmp(other_rank) {
Ordering::Equal => continue,
other => return other,
}
}
self.compare_kickers(&self_pairs, other, &other_pairs)
}
HandType::ThreeOfAKind => {
let self_three_rank = self.get_rank_by_count(3);
let other_three_rank = other.get_rank_by_count(3);
match self_three_rank.cmp(&other_three_rank) {
Ordering::Equal => {
self.compare_kickers(&[self_three_rank], other, &[other_three_rank])
}
other => other,
}
}
HandType::FullHouse => {
let self_three_rank = self.get_rank_by_count(3);
let other_three_rank = other.get_rank_by_count(3);
match self_three_rank.cmp(&other_three_rank) {
Ordering::Equal => {
let self_pair_rank = self.get_rank_by_count(2);
let other_pair_rank = other.get_rank_by_count(2);
self_pair_rank.cmp(&other_pair_rank)
}
other => other,
}
}
HandType::FourOfAKind => {
let self_four_rank = self.get_rank_by_count(4);
let other_four_rank = other.get_rank_by_count(4);
match self_four_rank.cmp(&other_four_rank) {
Ordering::Equal => {
self.compare_kickers(&[self_four_rank], other, &[other_four_rank])
}
other => other,
}
}
}
}
fn get_rank_by_count(&self, count: usize) -> Rank {
self.rank_counts
.iter()
.find(|(_, &c)| c == count)
.map(|(rank, _)| rank.clone())
.unwrap()
}
fn get_ranks_by_count(&self, count: usize) -> Vec<Rank> {
self.rank_counts
.iter()
.filter(|(_, &c)| c == count)
.map(|(rank, _)| rank.clone())
.collect()
}
fn compare_kickers(&self, exclude_ranks: &[Rank], other: &Self, other_exclude_ranks: &[Rank]) -> Ordering {
let mut self_ranks: Vec<Rank> = self.sorted_ranks.clone();
let mut other_ranks: Vec<Rank> = other.sorted_ranks.clone();
self_ranks.retain(|r| !exclude_ranks.contains(r));
other_ranks.retain(|r| !other_exclude_ranks.contains(r));
for (self_rank, other_rank) in self_ranks.iter().zip(other_ranks.iter()) {
match self_rank.cmp(other_rank) {
Ordering::Equal => continue,
other => return other,
}
}
Ordering::Equal
}
}
// 便利函数
pub fn analyze_hand(hand_str: &str) -> Result<String, String> {
let hand = Hand::from_str(hand_str)?;
Ok(format!("Hand: {} - Type: {}", hand_str, hand.hand_type.name()))
}
pub fn compare_two_hands(hand1_str: &str, hand2_str: &str) -> Result<String, String> {
let hand1 = Hand::from_str(hand1_str)?;
let hand2 = Hand::from_str(hand2_str)?;
match hand1.cmp(&hand2) {
Ordering::Greater => Ok(format!("{} wins over {}", hand1_str, hand2_str)),
Ordering::Less => Ok(format!("{} wins over {}", hand2_str, hand1_str)),
Ordering::Equal => Ok(format!("{} ties with {}", hand1_str, hand2_str)),
}
}
实际应用场景
扑克牌游戏在实际开发中有以下应用:
- 在线扑克平台:在线扑克游戏和锦标赛平台
- 手机游戏:移动端扑克游戏应用
- 社交应用:社交平台中的扑克游戏功能
- 教育软件:扑克规则教学和练习工具
- AI研究:扑克AI和游戏理论研究
- 赌场系统:数字赌场和博彩系统
- 数据分析:扑克策略分析和统计工具
- 娱乐应用:休闲娱乐类扑克应用
算法复杂度分析
-
时间复杂度:
- 单手牌分析:O(1)(固定5张牌)
- 多手牌比较:O(n log n),其中n是手牌数量(主要是排序开销)
-
空间复杂度:O(n)
- 其中n是手牌数量,需要存储所有手牌的解析结果
与其他实现方式的比较
rust
// 使用第三方库的实现
// [dependencies]
// cardgame = "0.1"
pub fn winning_hands_with_library<'a>(hands: &[&'a str]) -> Vec<&'a str> {
// 使用第三方扑克牌库实现
// 这里只是一个示例,实际实现会更复杂
unimplemented!()
}
// 使用宏的实现
macro_rules! poker_hand {
($($card:tt),*) => {
// 宏实现的扑克手牌
};
}
// 使用trait的实现
pub trait PokerHand {
fn hand_type(&self) -> HandType;
fn compare(&self, other: &Self) -> Ordering;
}
// 函数式实现
pub fn winning_hands_functional<'a>(hands: &[&'a str]) -> Vec<&'a str> {
hands
.iter()
.map(|&hand_str| (Hand::from_str(hand_str), hand_str))
.collect::<Vec<_>>()
.as_mut_slice()
.sort_by(|a, b| b.0.cmp(&a.0));
let winning_hand = &parsed_hands[0].0;
hands
.iter()
.filter(|&&hand_str| Hand::from_str(hand_str).cmp(winning_hand) == Ordering::Equal)
.copied()
.collect()
}
总结
通过 poker 练习,我们学到了:
- 复杂规则实现:掌握了如何实现复杂的扑克牌规则和比较逻辑
- 枚举设计:学会了使用枚举表示复杂的分类和状态
- 模式匹配:深入理解了Rust中模式匹配的强大功能
- 排序算法:理解了自定义排序规则的实现方法
- 错误处理:学会了处理各种输入错误和边界情况
- 性能优化:了解了如何优化数据结构和算法以提高性能
这些技能在实际开发中非常有用,特别是在游戏开发、规则引擎、复杂业务逻辑处理等场景中。扑克牌游戏虽然是一个具体的应用问题,但它涉及到了枚举设计、模式匹配、排序算法、错误处理、性能优化等许多核心概念,是学习Rust高级编程的良好起点。
通过这个练习,我们也看到了Rust在实现复杂业务规则和处理复杂数据结构方面的强大能力,以及如何用安全且高效的方式实现复杂的比较逻辑。这种结合了安全性和性能的语言特性正是Rust的魅力所在。