Avatar of helenst
0
1
Genius
0
0

helenst's solution

to Poker in the Python Track

Instructions
Test suite
Solution

Pick the best hand(s) from a list of poker hands.

See wikipedia for an overview of poker hands.

Exception messages

Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include a message.

To raise a message with an exception, just write it as an argument to the exception type. For example, instead of raise Exception, you should write:

raise Exception("Meaningful message indicating the source of the error")

Running the tests

To run the tests, run the appropriate command below (why they are different):

  • Python 2.7: py.test poker_test.py
  • Python 3.4+: pytest poker_test.py

Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version): python -m pytest poker_test.py

Common pytest options

  • -v : enable verbose output
  • -x : stop running tests on first failure
  • --ff : run failures from previous test before running other test cases

For other options, see python -m pytest -h

Submitting Exercises

Note that, when trying to submit an exercise, make sure the solution is in the $EXERCISM_WORKSPACE/python/poker directory.

You can find your Exercism workspace by running exercism debug and looking for the line that starts with Workspace.

For more detailed information about running tests, code style and linting, please see the help page.

Source

Inspired by the training course from Udacity. https://www.udacity.com/course/viewer#!/c-cs212/

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

poker_test.py

import unittest

from poker import best_hands


# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.0

class PokerTest(unittest.TestCase):
    def test_single_hand_always_wins(self):
        hands = ["4S 5S 7H 8D JC"]
        expected = ["4S 5S 7H 8D JC"]
        self.assertEqual(best_hands(hands), expected)

    def test_highest_card_out_of_all_hands_wins(self):
        hands = [
            "4D 5S 6S 8D 3C",
            "2S 4C 7S 9H 10H",
            "3S 4S 5D 6H JH",
        ]
        expected = ["3S 4S 5D 6H JH"]
        self.assertEqual(best_hands(hands), expected)

    def test_tie_has_multiple_winners(self):
        hands = [
            "4D 5S 6S 8D 3C",
            "2S 4C 7S 9H 10H",
            "3S 4S 5D 6H JH",
            "3H 4H 5C 6C JD",
        ]
        expected = [
            "3S 4S 5D 6H JH",
            "3H 4H 5C 6C JD",
        ]
        self.assertEqual(best_hands(hands), expected)

    def test_tie_compares_multiple(self):
        hands = [
            "3S 5H 6S 8D 7H",
            "2S 5D 6D 8C 7S",
        ]
        expected = ["3S 5H 6S 8D 7H"]
        self.assertEqual(best_hands(hands), expected)

    def test_one_pair_beats_high_card(self):
        hands = [
            "4S 5H 6C 8D KH",
            "2S 4H 6S 4D JH",
        ]
        expected = ["2S 4H 6S 4D JH"]
        self.assertEqual(best_hands(hands), expected)

    def test_highest_pair_wins(self):
        hands = [
            "4S 2H 6S 2D JH",
            "2S 4H 6C 4D JD",
        ]
        expected = ["2S 4H 6C 4D JD"]
        self.assertEqual(best_hands(hands), expected)

    def test_two_pairs_beats_one_pair(self):
        hands = [
            "2S 8H 6S 8D JH",
            "4S 5H 4C 8C 5C",
        ]
        expected = ["4S 5H 4C 8C 5C"]
        self.assertEqual(best_hands(hands), expected)

    def test_two_double_pair(self):
        hands = [
            "2S 8H 2D 8D 3H",
            "4S 5H 4C 8S 5D",
        ]
        expected = ["2S 8H 2D 8D 3H"]
        self.assertEqual(best_hands(hands), expected)

    def test_two_double_pair_higher_tie(self):
        hands = [
            "2S QS 2C QD JH",
            "JD QH JS 8D QC",
        ]
        expected = ["JD QH JS 8D QC"]
        self.assertEqual(best_hands(hands), expected)

    def test_two_double_pair_tie_kicker(self):
        hands = [
            "JD QH JS 8D QC",
            "JS QS JC 2D QD",
        ]
        expected = ["JD QH JS 8D QC"]
        self.assertEqual(best_hands(hands), expected)

    def test_three_of_a_kind_beats_two_pair(self):
        hands = [
            "2S 8H 2H 8D JH",
            "4S 5H 4C 8S 4H",
        ]
        expected = ["4S 5H 4C 8S 4H"]
        self.assertEqual(best_hands(hands), expected)

    def test_two_triple_pair(self):
        hands = [
            "2S 2H 2C 8D JH",
            "4S AH AS 8C AD",
        ]
        expected = ["4S AH AS 8C AD"]
        self.assertEqual(best_hands(hands), expected)

    def test_two_three_multiple_decks(self):
        hands = [
            "4S AH AS 7C AD",
            "4S AH AS 8C AD",
        ]
        expected = ["4S AH AS 8C AD"]
        self.assertEqual(best_hands(hands), expected)

    def test_three_vs_straight(self):
        hands = [
            "4S 5H 4C 8D 4H",
            "3S 4D 2S 6D 5C",
        ]
        expected = ["3S 4D 2S 6D 5C"]
        self.assertEqual(best_hands(hands), expected)

    def test_aces_can_end_straight(self):
        hands = [
            "4S 5H 4C 8D 4H",
            "10D JH QS KD AC",
        ]
        expected = ["10D JH QS KD AC"]
        self.assertEqual(best_hands(hands), expected)

    def test_aces_can_start_straight(self):
        hands = [
            "4S 5H 4C 8D 4H",
            "4D AH 3S 2D 5C",
        ]
        expected = ["4D AH 3S 2D 5C"]
        self.assertEqual(best_hands(hands), expected)

    def test_two_straights(self):
        hands = [
            "4S 6C 7S 8D 5H",
            "5S 7H 8S 9D 6H",
        ]
        expected = ["5S 7H 8S 9D 6H"]
        self.assertEqual(best_hands(hands), expected)

    def test_lowest_straight(self):
        hands = [
            "2H 3C 4D 5D 6H",
            "4S AH 3S 2D 5H",
        ]
        expected = ["2H 3C 4D 5D 6H"]
        self.assertEqual(best_hands(hands), expected)

    def test_straight_vs_flush(self):
        hands = [
            "4C 6H 7D 8D 5H",
            "2S 4S 5S 6S 7S",
        ]
        expected = ["2S 4S 5S 6S 7S"]
        self.assertEqual(best_hands(hands), expected)

    def test_two_flushes(self):
        hands = [
            "4H 7H 8H 9H 6H",
            "2S 4S 5S 6S 7S",
        ]
        expected = ["4H 7H 8H 9H 6H"]
        self.assertEqual(best_hands(hands), expected)

    def test_flush_vs_full(self):
        hands = [
            "3H 6H 7H 8H 5H",
            "4S 5H 4C 5D 4H",
        ]
        expected = ["4S 5H 4C 5D 4H"]
        self.assertEqual(best_hands(hands), expected)

    def test_two_fulls(self):
        hands = [
            "4H 4S 4D 9S 9D",
            "5H 5S 5D 8S 8D",
        ]
        expected = ["5H 5S 5D 8S 8D"]
        self.assertEqual(best_hands(hands), expected)

    def test_two_fulls_same_triplet(self):
        hands = [
            "5H 5S 5D 9S 9D",
            "5H 5S 5D 8S 8D",
        ]
        expected = ["5H 5S 5D 9S 9D"]
        self.assertEqual(best_hands(hands), expected)

    def test_full_vs_four(self):
        hands = [
            "4S 5H 4D 5D 4H",
            "3S 3H 2S 3D 3C",
        ]
        expected = ["3S 3H 2S 3D 3C"]
        self.assertEqual(best_hands(hands), expected)

    def test_two_fours(self):
        hands = [
            "2S 2H 2C 8D 2D",
            "4S 5H 5S 5D 5C",
        ]
        expected = ["4S 5H 5S 5D 5C"]
        self.assertEqual(best_hands(hands), expected)

    def test_two_fours_kicker(self):
        hands = [
            "3S 3H 2S 3D 3C",
            "3S 3H 4S 3D 3C",
        ]
        expected = ["3S 3H 4S 3D 3C"]
        self.assertEqual(best_hands(hands), expected)

    def test_four_vs_straight_flush(self):
        hands = [
            "4S 5H 5S 5D 5C",
            "7S 8S 9S 6S 10S",
        ]
        expected = ["7S 8S 9S 6S 10S"]
        self.assertEqual(best_hands(hands), expected)

    def test_two_straight_flushes(self):
        hands = [
            "4H 6H 7H 8H 5H",
            "5S 7S 8S 9S 6S",
        ]
        expected = ["5S 7S 8S 9S 6S"]
        self.assertEqual(best_hands(hands), expected)


if __name__ == '__main__':
    unittest.main()
from collections import Counter


def invert_dict(d):
    return {v: k for (k, v) in d.items()}


def lazy_property(fn):
    attr_name = '_lazy_' + fn.__name__

    @property
    def _lazy_property(self):
        if not hasattr(self, attr_name):
            setattr(self, attr_name, fn(self))
        return getattr(self, attr_name)
    return _lazy_property


class Card:
    VALUES = dict(zip('123456789TJQKA', range(1, 15)))

    def __init__(self, card):
        self.rank, self.suit = card
        self.value = self.VALUES[self.rank]

    def __repr__(self):
        return ''.join((self.rank, self.suit))


class Hand:
    def __init__(self, cards):
        self.cards = list(map(Card, cards))

    def __repr__(self):
        return ' '.join(map(str, self.cards))

    @lazy_property
    def card_list(self):
        return str(self).split()

    @lazy_property
    def values(self):
        """
        Return the card values
        """
        return [card.value for card in self.cards]

    @lazy_property
    def suits(self):
        """
        Return the card suits
        """
        return [card.suit for card in self.cards]

    @lazy_property
    def value_counts(self):
        """
        Count the values of the cards
        """
        return Counter(self.values)

    def groups(self, size):
        """
        List groups of a required size
        """
        return [
            key
            for (key, value) in self.value_counts.items()
            if value == size
        ]

    @lazy_property
    def pairs(self):
        """
        Return all the pairs
        """
        return self.groups(2)

    @lazy_property
    def single_pair(self):
        """
        If there are just 2 cards of equal value (a pair)
        return that value

        Return 0 if there is no pair, or more than one pair
        """
        # If there's no pair, max_hand is 0
        return (self.pairs[0] if len(self.pairs) == 1 else 0)

    @lazy_property
    def double_pair(self):
        """
        If there are two pairs, return their values
        sorted in descending order

        (e.g. 4, 3, 3, 4, 5 -> (4, 3))

        Return (0, 0) if there are not two pairs.
        """
        return (
            tuple(sorted(self.pairs, reverse=True))
            if len(self.pairs) == 2
            else (0, 0)
        )

    @lazy_property
    def triple(self):
        """
        If there are 3 cards of equal value (a triple)
        return that value

        Return 0 if there is no triple
        """
        triples = self.groups(3)
        return triples[0] if triples else 0

    @lazy_property
    def square(self):
        """
        If there are 4 cards of equal value (a square)
        return that value

        Return 0 if there is no square
        """
        squares = self.groups(4)
        return squares[0] if squares else 0

    @lazy_property
    def straight(self):
        """
        If the hand consists of 5 consecutive valued cards,
        return the starting value.

        Return 0 if there is no straight.
        """
        values = sorted(self.values)
        first = values[0]
        is_straight = (values == list(range(first, first+5)))
        return first if is_straight else 0

    @lazy_property
    def flush(self):
        """
        If there's a flush, return its highest value.

        Return 0 if there is no flush.
        """
        return max(self.values) if len(set(self.suits)) == 1 else 0

    @lazy_property
    def straight_flush(self):
        """
        If there's a straight flush (i.e. there's a straight and a flush)
        return its highest value.

        Return 0 if there is not a straight flush.
        """
        return self.flush if self.straight and self.flush else 0

    @lazy_property
    def full(self):
        """
        If there's a full house (i.e. a triple and a pair)
        return their values (with the triple value first)

        Return (0, 0) if there is not a full house.
        """
        pair, triple = self.single_pair, self.triple
        return (triple, pair) if triple and pair else (0, 0)

    @property
    def score(self):
        """
        Create a score for this hand.

        This is a tuple of the various possible hand scores,
        with the highest priority first, to take advantage of Python's
        built in tuple comparison.
        """
        return (
            self.straight_flush,
            self.square,
            self.full,
            self.flush,
            self.straight,
            self.triple,
            self.double_pair,
            self.single_pair,
        )

    def __gt__(self, other):
        return self.score > other.score


def poker(hands):
    hands = [Hand(cards) for cards in hands]
    max_score = max(hands).score
    return [
        hand.card_list
        for hand in hands
        if hand.score == max_score
    ]

What can you learn from this solution?

A huge amount can be learnt from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that I could read more about to develop my understanding?