Poker
1. Readme
扑克
从扑克手牌列表中,挑选最好的手牌。
见wikipedia中的扑克手牌概述.
提示
- 排名扑克手牌可以被认为是一个排序问题.
- Rust 提供sort方法,用在
Vec<T> where T: Ord
。 Ord
types是一个总顺序形式,a < b
,a == b
或a > b
其中一个一定是真的.- 扑克手牌不符合一个总顺序:两份手牌可以不相等,但有相同的排序。例子:
3S 4S 5D 6H JH"
,"3H 4H 5C 6C JD"
。 - Rust 提供
PartialOrd
trait处理不具有完全顺序的可排序事物的情况。然而,它没有提供标准的sort
方法,用于Vec<T> where T: PartialOrd
。在这种情况下,对向量进行排序的标准方式是your_vec.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::{Less|Equal|Greater}));
这取决于你的需要.` - 您可以考虑实现了
PartialOrd
,表示扑克手牌的类型。
资源
受来自 Udacity 的培训课程的启发.https://www.udacity.com/course/viewer#!/c-cs212/
2. 开始你的表演
/// 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]) -> Option<Vec<&'a str>> { unimplemented!("Out of {:?}, which hand wins?", hands) }
3. 测试代码查看
# #![allow(unused_variables)] #fn main() { // use std::collections::HashSet; fn hs_from<'a>(input: &[&'a str]) -> HashSet<&'a str> { let mut hs = HashSet::new(); for item in input.iter() { hs.insert(*item); } hs } /// Test that the expected output is produced from the given input /// using the `winning_hands` function. /// /// Note that the output can be in any order. Here, we use a HashSet to /// abstract away the order of outputs. fn test<'a, 'b>(input: &[&'a str], expected: &[&'b str]) { assert_eq!( hs_from(&winning_hands(input).expect("This test should produce Some value",)), hs_from(expected) ) } #[test] fn test_single_hand_always_wins() { test(&["4S 5S 7H 8D JC"], &["4S 5S 7H 8D JC"]) } #[test] //#[ignore] 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"], ) } #[test] //#[ignore] 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] //#[ignore] 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"]) } #[test] //#[ignore] fn test_one_pair_beats_high_card() { test(&["4S 5H 6C 8D KH", "2S 4H 6S 4D JH"], &["2S 4H 6S 4D JH"]) } #[test] //#[ignore] fn test_highest_pair_wins() { test(&["4S 2H 6S 2D JH", "2S 4H 6C 4D JD"], &["2S 4H 6C 4D JD"]) } #[test] //#[ignore] fn test_two_pairs_beats_one_pair() { test(&["2S 8H 6S 8D JH", "4S 5H 4C 8C 5C"], &["4S 5H 4C 8C 5C"]) } #[test] //#[ignore] 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"]) } #[test] //#[ignore] 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"]) } #[test] //#[ignore] 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"]) } #[test] //#[ignore] 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] //#[ignore] 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"]) } #[test] //#[ignore] 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"]) } #[test] //#[ignore] 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] //#[ignore] 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"]) } #[test] //#[ignore] 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"]) } #[test] //#[ignore] 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"]) } #[test] //#[ignore] 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"]) } #[test] //#[ignore] fn test_flush_beats_a_straight() { test(&["4C 6H 7D 8D 5H", "2S 4S 5S 6S 7S"], &["2S 4S 5S 6S 7S"]) } #[test] //#[ignore] 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"]) } #[test] //#[ignore] fn test_full_house_beats_a_flush() { test(&["3H 6H 7H 8H 5H", "4S 5C 4C 5D 4H"], &["4S 5C 4C 5D 4H"]) } #[test] //#[ignore] 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"]) } #[test] //#[ignore] 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"]) } #[test] //#[ignore] 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] //#[ignore] 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"]) } #[test] //#[ignore] 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"]) } #[test] //#[ignore] 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] //#[ignore] 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"]) } #}
4. 答案
# #![allow(unused_variables)] #fn main() { use std::cmp::Ordering; use std::fmt; #[macro_use] extern crate try_opt; extern crate counter; use counter::Counter; /// 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]) -> Option<Vec<&'a str>> { let mut hands = try_opt!( hands .iter() .map(|source| Hand::try_from(source)) .collect::<Option<Vec<Hand>>>() ); hands.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Less)); hands.last().map(|last| { hands .iter() .rev() .take_while(|&item| item.partial_cmp(last) == Some(Ordering::Equal)) .map(|hand| hand.source) .collect() }) } #[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy, Hash)] enum Suit { Spades, Clubs, Diamonds, Hearts, } impl Suit { fn try_from(source: &str) -> Option<Suit> { use Suit::*; match source { "S" => Some(Spades), "C" => Some(Clubs), "D" => Some(Diamonds), "H" => Some(Hearts), _ => None, } } } impl fmt::Display for Suit { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use Suit::*; write!( f, "{}", match *self { Spades => "S", Clubs => "C", Diamonds => "D", Hearts => "H", } ) } } #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] enum Rank { Number(u8), Jack, Queen, King, Ace, } impl Rank { fn try_from(source: &str) -> Option<Rank> { use Rank::*; match source { "A" => Some(Ace), "K" => Some(King), "Q" => Some(Queen), "J" => Some(Jack), "10" => Some(Number(10)), "9" => Some(Number(9)), "8" => Some(Number(8)), "7" => Some(Number(7)), "6" => Some(Number(6)), "5" => Some(Number(5)), "4" => Some(Number(4)), "3" => Some(Number(3)), "2" => Some(Number(2)), _ => None, } } fn value(&self) -> usize { use Rank::*; match *self { Ace => 14, King => 13, Queen => 12, Jack => 11, Number(n) => n as usize, } } } impl fmt::Display for Rank { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use Rank::*; let num_str; // early declaration to placate NLL of Number case write!( f, "{}", match *self { Ace => "A", King => "K", Queen => "Q", Jack => "J", Number(n) => { num_str = n.to_string(); &num_str } } ) } } impl PartialOrd for Rank { fn partial_cmp(&self, other: &Rank) -> Option<Ordering> { Some(self.value().cmp(&other.value())) } } #[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy)] struct Card { rank: Rank, suit: Suit, } impl Card { fn try_from_split(source: &str, split: usize) -> Option<Card> { Some(Card { rank: try_opt!(Rank::try_from(&source[..split])), suit: try_opt!(Suit::try_from(&source[split..])), }) } fn try_from(source: &str) -> Option<Card> { match source.len() { 3 => Card::try_from_split(source, 2), 2 => Card::try_from_split(source, 1), _ => None, } } } impl fmt::Display for Card { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}{}", self.rank, self.suit) } } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] enum PokerHand { HighCard, OnePair, TwoPair, ThreeOfAKind, Straight, Flush, FullHouse, FourOfAKind, StraightFlush, } impl PokerHand { fn is_ace_low_straight(cards: &[Card]) -> bool { // special case: ace-low straight // still depends on the sorted precondition cards[0].rank.value() == 2 && cards[4].rank == Rank::Ace && cards .windows(2) .take(3) // (0, 1), (1, 2), (2, 3) --> skips 4, ace .map(|pair| pair[1].rank.value() - pair[0].rank.value()) .all(|diff| diff == 1) } fn analyze(cards: &[Card]) -> Option<PokerHand> { if cards.len() == 5 { let suit_counter = Counter::init(cards.iter().map(|c| c.suit)); let is_flush = suit_counter .most_common() .map(|(_suit, count)| count) .next() == Some(5); // Note that `is_straight` depends on a precondition: it only works // if the input `cards` are sorted by rank value ascending. let is_straight = cards .windows(2) .map(|pair| pair[1].rank.value() - pair[0].rank.value()) .all(|diff| diff == 1) || PokerHand::is_ace_low_straight(cards); if is_flush && is_straight { return Some(PokerHand::StraightFlush); } let rank_counter = Counter::init(cards.iter().map(|c| c.rank)); let mut rc_iter = rank_counter.most_common().map(|(_rank, count)| count); let rc_most = rc_iter.next(); let rc_second = rc_iter.next(); if rc_most == Some(4) { return Some(PokerHand::FourOfAKind); } if rc_most == Some(3) && rc_second == Some(2) { return Some(PokerHand::FullHouse); } if is_flush { return Some(PokerHand::Flush); } if is_straight { return Some(PokerHand::Straight); } if rc_most == Some(3) { return Some(PokerHand::ThreeOfAKind); } if rc_most == Some(2) && rc_second == Some(2) { return Some(PokerHand::TwoPair); } if rc_most == Some(2) { return Some(PokerHand::OnePair); } Some(PokerHand::HighCard) } else { None } } } #[derive(Debug, PartialEq, Eq)] struct Hand<'a> { source: &'a str, cards: [Card; 5], hand_type: PokerHand, } impl<'a> Hand<'a> { fn try_from(source: &'a str) -> Option<Hand> { let mut cards = try_opt!( source .split_whitespace() .map(|s| Card::try_from(s)) .collect::<Option<Vec<Card>>>() ); cards.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Less)); if cards.len() == 5 { Some(Hand { source: source, cards: [cards[0], cards[1], cards[2], cards[3], cards[4]], hand_type: try_opt!(PokerHand::analyze(&cards)), }) } else { None } } fn cmp_high_card(&self, other: &Hand, card: usize) -> Ordering { let mut ordering = self.cards[card] .rank .value() .cmp(&other.cards[card].rank.value()); if card != 0 { ordering = ordering.then_with(|| self.cmp_high_card(other, card - 1)); } ordering } fn value_by_frequency(&self) -> (Option<Rank>, Option<Rank>, Option<Rank>) { let rank_counter = Counter::init(self.cards.iter().map(|c| c.rank)); let mut rc_iter = rank_counter .most_common_tiebreaker(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Less)) .map(|(rank, _count)| rank); (rc_iter.next(), rc_iter.next(), rc_iter.next()) } fn cmp_cascade_by_freq(&self, other: &Hand) -> Ordering { let (s1, s2, s3) = self.value_by_frequency(); let (o1, o2, o3) = other.value_by_frequency(); s1.partial_cmp(&o1) .map(|c| { c.then( s2.partial_cmp(&o2) .map(|c2| c2.then(s3.partial_cmp(&o3).unwrap_or(Ordering::Equal))) .unwrap_or(Ordering::Equal), ) }) .unwrap_or(Ordering::Equal) } fn cmp_straight(&self, other: &Hand) -> Ordering { let s = if PokerHand::is_ace_low_straight(&self.cards) { 5 } else { self.cards[4].rank.value() }; let o = if PokerHand::is_ace_low_straight(&other.cards) { 5 } else { other.cards[4].rank.value() }; s.cmp(&o) } } impl<'a> fmt::Display for Hand<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.source) } } impl<'a> PartialOrd for Hand<'a> { fn partial_cmp(&self, other: &Hand) -> Option<Ordering> { Some(self.hand_type.cmp(&other.hand_type).then_with(|| { use PokerHand::*; match self.hand_type { HighCard => self.cmp_high_card(other, 4), OnePair => self.cmp_cascade_by_freq(other), TwoPair => self.cmp_cascade_by_freq(other), ThreeOfAKind => self.cmp_cascade_by_freq(other), Straight => self.cmp_straight(other), Flush => self.cmp_high_card(other, 4), FullHouse => self.cmp_cascade_by_freq(other), FourOfAKind => self.cmp_cascade_by_freq(other), StraightFlush => self.cmp_straight(other), } })) } } #}