Rust 练习册 :Poker与扑克牌游戏
扑克牌游戏是世界上最流行的纸牌游戏之一,具有复杂的规则和策略。在 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的同花顺
让我们先看看练习提供的函数签名:
/// 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. 基础实现
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. 优化实现
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}
}
测试用例分析
通过查看测试用例,我们可以更好地理解需求:
#[test]
fn test_single_hand_always_wins() {test(&["4S 5S 7H 8D JC"], &["4S 5S 7H 8D JC"])
}
单个手牌总是获胜。
#[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)
}
重复的手牌总是平局。
#[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)。
#[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"],)
}
平局时有多个获胜者。
#[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 cardtest(&["3S 5H 6S 8D 7H", "2S 5D 6D 8C 7S"], &["3S 5H 6S 8D 7H"])
}
当最高牌相同时,比较次高牌,以此类推。
#[test]
fn test_one_pair_beats_high_card() {test(&["4S 5H 6C 8D KH", "2S 4H 6S 4D JH"], &["2S 4H 6S 4D JH"])
}
一对牌胜过高牌。
#[test]
fn test_highest_pair_wins() {test(&["4S 2H 6S 2D JH", "2S 4H 6C 4D JD"], &["2S 4H 6C 4D JD"])
}
比较对子的点数,高对子获胜。
#[test]
fn test_two_pairs_beats_one_pair() {test(&["2S 8H 6S 8D JH", "4S 5H 4C 8C 5C"], &["4S 5H 4C 8C 5C"])
}
两对胜过一对。
#[test]
fn test_two_pair_ranks() {// both hands have two pairs, highest ranked pair winstest(&["2S 8H 2D 8D 3H", "4S 5H 4C 8S 5D"], &["2S 8H 2D 8D 3H"])
}
两对牌比较时,首先比较最高对子。
#[test]
fn test_two_pairs_second_pair_cascade() {// both hands have two pairs, with the same highest ranked pair,// tie goes to low pairtest(&["2S QS 2C QD JH", "JD QH JS 8D QC"], &["JD QH JS 8D QC"])
}
最高对子相同时,比较次高对子。
#[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)。
#[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"])
}
三条胜过两对。
#[test]
fn test_three_of_a_kind_ranks() {//both hands have three of a kind, tie goes to highest ranked triplettest(&["2S 2H 2C 8D JH", "4S AH AS 8C AD"], &["4S AH AS 8C AD"])
}
三条比较时,点数高的三条获胜。
#[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 cardstest(&["4S AH AS 7C AD", "4S AH AS 8C AD"], &["4S AH AS 8C AD"])
}
三条相同时,比较剩余牌的点数。
#[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"])
}
顺子胜过三条。
#[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)。
#[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)。
#[test]
fn test_straight_cascade() {// both hands with a straight, tie goes to highest ranked cardtest(&["4S 6C 7S 8D 5H", "5S 7H 8S 9D 6H"], &["5S 7H 8S 9D 6H"])
}
顺子比较时,点数高的顺子获胜。
#[test]
fn test_straight_scoring() {// even though an ace is usually high, a 5-high straight is the lowest-scoring straighttest(&["2H 3C 4D 5D 6H", "4S AH 3S 2D 5H"], &["2H 3C 4D 5D 6H"])
}
尽管A通常作为高牌,但5-high顺子(A 2 3 4 5)是最低的顺子。
#[test]
fn test_flush_beats_a_straight() {test(&["4C 6H 7D 8D 5H", "2S 4S 5S 6S 7S"], &["2S 4S 5S 6S 7S"])
}
同花胜过顺子。
#[test]
fn test_flush_cascade() {// both hands have a flush, tie goes to high card, down to the last one if necessarytest(&["4H 7H 8H 9H 6H", "2S 4S 5S 6S 7S"], &["4H 7H 8H 9H 6H"])
}
同花比较时,比较最高牌,然后依次比较。
#[test]
fn test_full_house_beats_a_flush() {test(&["3H 6H 7H 8H 5H", "4S 5C 4C 5D 4H"], &["4S 5C 4C 5D 4H"])
}
葫芦胜过同花。
#[test]
fn test_full_house_ranks() {// both hands have a full house, tie goes to highest-ranked triplettest(&["4H 4S 4D 9S 9D", "5H 5S 5D 8S 8D"], &["5H 5S 5D 8S 8D"])
}
葫芦比较时,三条点数高的获胜。
#[test]
fn test_full_house_cascade() {// with multiple decks, both hands have a full house with the same triplet, tie goes to the pairtest(&["5H 5S 5D 9S 9D", "5H 5S 5D 8S 8D"], &["5H 5S 5D 9S 9D"])
}
三条相同时,比较对子点数。
#[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"])
}
四条胜过葫芦。
#[test]
fn test_four_of_a_kind_ranks() {// both hands have four of a kind, tie goes to high quadtest(&["2S 2H 2C 8D 2D", "4S 5H 5S 5D 5C"], &["4S 5H 5S 5D 5C"])
}
四条比较时,点数高的四条获胜。
#[test]
fn test_four_of_a_kind_cascade() {// with multiple decks, both hands with identical four of a kind, tie determined by kickertest(&["3S 3H 2S 3D 3C", "3S 3H 4S 3D 3C"], &["3S 3H 4S 3D 3C"])
}
四条相同时,比较剩余牌(kicker)。
#[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"])
}
同花顺胜过四条。
#[test]
fn test_straight_flush_ranks() {// both hands have straight flush, tie goes to highest-ranked cardtest(&["4H 6H 7H 8H 5H", "5S 7S 8S 9S 6S"], &["5S 7S 8S 9S 6S"])
}
同花顺比较时,点数高的获胜。
性能优化版本
考虑性能的优化实现:
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::Acesorted_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 {// 安全地将索引转换为Rankreturn 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 {// 安全地将索引转换为Rankranks.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}
}
错误处理和边界情况
考虑更多边界情况的实现:
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}
}
扩展功能
基于基础实现,我们可以添加更多功能:
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是手牌数量,需要存储所有手牌的解析结果
与其他实现方式的比较
// 使用第三方库的实现
// [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的魅力所在。
