Poker hand

See the original problem on HackerRank.

Solutions

The solutions revolve around finding the frequency of the card values. In addition, checking flush requires us to verify all the suits are equal. This check can be done in advance or while counting the frequency of each card.

Afterwards, mapping the card frequency to the score is straighforward: find the maximum frequency and that will give the score: 1 is “High Card” 2 is “Pair”, 3, 4 and 5 are “Three of a Kind”).

For simplicity with indexes, we keep the frequency of the card ‘0’ even though it does not make any sense.

In this first solution, flush is checked in advanced:

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  vector ranks(5); // alternatively, using std::array is possible vector suits(5); // alternatively, using std::array is possible copy_n(istream_iterator(cin), 5, begin(ranks)); copy_n(istream_iterator(cin), 5, begin(suits)); if (adjacent_find(begin(suits), end(suits), not_equal_to<>{}) == end(suits)) return "Flush"; array freq{}; for (auto c : ranks) freq[c]++; auto max = *max_element(begin(freq), end(freq)); static const std::map hands = { {1, "High Card"}, {2, "Pair"}, {3, "Three of a Kind"}, {4, "Three of a Kind"} }; return hands.at(max); 

In this variation, checking flush does not require an extra iteration:

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20  array freq{}; bool flush = true; for (auto i=1u; i hands = { {1, "High Card"}, {2, "Pair"}, {3, "Three of a Kind"}, {4, "Three of a Kind"} }; return hands.at(max); 

Here is a compact solution in Python by mrv96:

  1 2 3 4 5 6 7 8 9 10 11 12 13  numbers = input().split() semi = input().split() max_nums = max(numbers.count(el) for el in numbers) if len(set(semi)) == 1: print('Flush') elif max_nums >= 3: print('Three of a Kind') elif max_nums == 2: print('Pair') else: print('High Card') 

In this variant, the “Flush” is checked by comparing the only possible suit strings as they are limited:

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  import sys nums = input().split(" ") colors = "".join(input().split(" ")) def hist(arr): ist = [0 for _ in range(13)] for n in arr: ist[int(n) - 1] += 1 return ist ist = hist(nums) if colors == "aaaaa" or colors == "bbbbb" or colors == "ccccc" or colors == "ddddd": print("Flush") elif max(ist) >= 3: print("Three of a Kind") elif max(ist) == 2: print("Pair") else: print("High Card") 

Design considerations

The full “Poker” game is more interesting as it includes other scores, such as:

• double pair
• full house
• poker
• straight
• straigh flush

A simple technique consists in calculating the “histogram” of the poker hand that represents how many cards have a certain frequency. For example, suppose we have such an input

 1 2  1 5 3 1 3 a a b c d 

The card frequencies follow:

 1  0 2 0 2 0 1 0 ... 

The histogram “compacts” these numbers as follow:

 1  0 1 2 0 0 

In other words:

• there are zero card values occurring zero times (this is to simplify indexes)
• there is one card value occurring once (5)
• there are two card values occurring twice (1 and 3)
• there are zero card values occurring three times
• there are zero card values occurring four times

Another example:

 1 2  2 2 2 3 3 a b c d c 

Frequencies:

 1  0 0 3 2 0 0 0 ... 

Histogram:

 1  0 1 1 0 0 

In other words, there is one card value occurring twice and one occurring three times (it’s a full house).

You see that the histogram allows us to find most of the scores easily:

• poker: histogram[4] is 1
• full house: histogram[3] and histogram[2] are both 1
• three of a kind: histogram[3] is 1
• double pair: histogram[2] is 2
• pair: histogram[2] is 1
• high card: histogram[1] is not zero

To check against straight and royal straight, the histogram is not of much help (it would be in similar games like Yahtzee). A possible way consists in sorting the card values in ascending order and checking if the car values are part of any of the two predefined straight patterns:

 1 2 3 4 5 6 7 8  bool is_straight(hand hand) { static const std::array straight { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 }; static const std::array straight_to_ace { 1, 10, 11, 12, 13 }; sort(begin(hand), end(hand)); return std::search(begin(straight), end(straight), begin(hand), end(hand))!= end(straight)) || std::equal(begin(straight_to_ace), end(straight_to_ace), begin(hand), end(hand); } 

An alternative consists in checking if either the array does not contain any adjacent pair whose difference is greater than 1 or is exactly the predefined straight “10 to ace”:

 1 2 3 4 5 6  bool is_straight_variant(hand cards) { std::sort(begin(cards), end(cards)); return std::adjacent_find(begin(cards), end(cards), [](auto i, auto j){ return j - i > 1; }) == end(cards) || cards == std::array{1, 10, 11, 12, 13}; } 

Just for fun, here is a variant that, after sorting, iterates the array only once:

 1 2 3 4 5 6  bool is_straight_variant(std::array cards) { std::sort(begin(cards), end(cards)); return std::adjacent_find(std::next(begin(cards)), end(cards), [](auto i, auto j){ return j - i > 1; }) == end(cards) && (cards[1]-cards[0]==1 || (cards[0]==1 && cards[1]==10)); } 

Thus, all the “check rules” could be encapsulated in functions and applied in a specified order, in a sort of “chain of responsibility” fashion. We propose an implementation based on std::optional:

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104  using histogram_t = std::array; using cards_t = std::array; struct hand { cards_t cards; histogram_t histo; bool is_flush; }; bool is_straight(hand hand) { static const std::array straight { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 }; static const std::array straight_to_ace { 1, 10, 11, 12, 13 }; sort(begin(hand), end(hand)); return std::search(begin(straight), end(straight), begin(hand), end(hand))!= end(straight)) || std::equal(begin(straight_to_ace), end(straight_to_ace), begin(hand), end(hand); } std::optional maybe_straight_flush(const hand& hand) { return hand.histo[0] == 5 && hand.is_flush && is_straight(hand.cards) ? std::optional{"Straight Flush"} : std::nullopt; } std::optional maybe_poker(const hand& hand) { return hand.histo[4] ? std::optional{"Poker"} : std::nullopt; } std::optional maybe_full(const hand& hand) { return hand.histo[2] && hand.histo[3] ? std::optional{"Full House"} : std::nullopt; } std::optional maybe_flush(const hand& hand) { return hand.is_flush ? std::optional{"Flush"} : std::nullopt; } std::optional maybe_straight(const hand& hand) { return hand.histo[1] == 5 && is_straight(hand.cards) ? std::optional{"Straight"} : std::nullopt; } std::optional maybe_three_of_a_kind(const hand& hand) { return hand.histo[3] ? std::optional{"Three of a Kind"} : std::nullopt; } std::optional maybe_double_pair(const hand& hand) { return hand.histo[2] == 2 ? std::optional{"Double Pair"} : std::nullopt; } std::optional maybe_pair(const hand& hand) { return hand.histo[2] ? std::optional{"Pair"} : std::nullopt; } std::optional maybe_high_card(const hand& hand) { return hand.histo[1] ? std::optional{"High Card"} : std::nullopt; } std::string calculate_hand(const hand& h) { std::array rules = {maybe_straight_flush, maybe_poker, maybe_full, maybe_flush, maybe_straight, maybe_three_of_a_kind, maybe_double_pair, maybe_pair, maybe_high_card}; std::optional res; auto it = begin(rules); while (!res) { res = (*it++)(h); } return *res; } int main() { array ranks{}; array suits{}; copy_n(istream_iterator(cin), 5, begin(ranks)); copy_n(istream_iterator(cin), 5, begin(suits)); array freq{}; bool flush = true; for (auto i=1u; i

The main point is: we search for the first “check rule” that returns a valued optional, otherwise we keep on searching. Clearly, at least the “high card” rule will succeed.

Then it’s possible to refactor this a bit more. For example:

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31  using rule_fn = std::function(const hand&)>; rule_fn make_check(const char* name, auto check) { return [=](const hand& h){ return check(h) ? std::optional{name} : std::nullopt; }; } std::string calculate_hand(const hand& h) { std::vector rules = { make_check("Straight Flush", [](const hand& h) { return h.histo[0] == 5 && h.is_flush && is_straight(h.cards); }), make_check("Poker", [](const hand& h) { return h.histo[4]; }), make_check("Full House", [](const hand& h) { return h.histo[3] && h.histo[2]; }), make_check("Flush", [](const hand& h) { return h.is_flush; }), make_check("Straight", [](const hand& h) { return h.histo[1] == 5 && is_straight(h.cards); }), make_check("Three of a Kind", [](const hand& h) { return h.histo[3]; }), make_check("Double Pair", [](const hand& h) { return h.histo[2] == 2; }), make_check("Pair", [](const hand& h) { return h.histo[2]; }), make_check("High Card", [](const hand& h) { return h.histo[1]; }) }; std::optional res; auto it = begin(rules); while (!res) { res = (*it++)(h); } return *res; } 

This approach allows to create and use rules dynamically.

Anyway, there exist design alternatives. What about trying others?

We've worked on this challenge in these gyms: modena