Poker

1. Readme

扑克

从扑克手牌列表中,挑选最好的手牌。

wikipedia中的扑克手牌概述.

提示

  • 排名扑克手牌可以被认为是一个排序问题.
  • Rust 提供sort方法,用在Vec<T> where T: Ord
  • Ord types是一个总顺序形式,a < b,a == ba > 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),
           }
       }))
   }
}

#}



填充/相关