cs212 ยป

CS212 - Unit 1: Winning Poker Hands

Contents

Outlining the Problem

Unit1- 2/

Writing a poker program is an example of a general process with three steps; understand, specify and design. The process involves starting with a vague understanding that you refine into a formal specification of a problem. You then further specify your understanding into something that is amenable to being coded. Then, after the design process you end up with working code.

  • Step 1: Understand
  • Start with a vague understanding that you refine into a problem. In this step you want to take inventory of the concepts you are dealing with. With respect to writing a poker problem, begin with the notion of a "hand," which is five cards. Additionally, note that each card has a "rank" and a "suit."

For example, a five of diamonds card has a rank of five and the suit is diamonds.

Another concept to identify is the "hand rank," which takes a hand and maps to details about the hand.

  • Step 2: Specify
  • Specify how this problem can be made amenable to being coded. The main program you are trying to specify is called poker and it takes a list of hands as input and outputs the best hand. The best hands are described here:

http://en.wikipedia.org/wiki/List_of_poker_hands

These rules dictate which hands beat which, that is, how each hand ranks relative to one another.

There are three concepts that make up the hand rank:

  1. Kind means that there are cards of the same rank. "n-kind," where n can be one of a kind, two of a kind or three of a kind.
  2. Straight means that there are five consecutive ranks and the suit does not matter. For example: a five of clubs, a six of spades, a seven of diamonds, an eight of spades and a nine of hearts. This is a straight because the rank of the cards is sequential.

  3. Flush means that all of the cards are the same suit and the ranks do not matter.

For example: a ten of diamonds, a seven of diamonds, five of diamonds, four of diamonds and two of diamonds.

Now you know about the types of data you are dealing with: hands, cards, ranks and suits. You also now know about the functions for them: n-kind, straight, flush. From here you can move on to step 3, the design phase.

Step 3: Design working code

  • That's what this course is all about!

Quiz: Representing Hands

Unit1-3/

Which of the possible representations for a hand makes the most sense. There may be more than one.

  • a. ['JS', 'JD', '2S', '2C', '7H']
  • b. [(11, 'S'), (11, 'D'), (2, 'S'), (2, 'C'), (7, 'H')]
  • c. set (['JJ', 'JD', '2S', '2C', '7H')])
  • d. "JS JD 2S 2C 7H"

Wild West Poker

Unit1-11/

A poker player will call out his hand when he has to reveal it. With this information you will know how to rank and assign the hand.

  • "Straight flush, Jack high!"

This is all you have to know about the hand; a straight flush and the highest ranking-Jack.

Here is a ranking table of the possible hands:

  • 0- High Card
  • 1- One Pair
  • 2- Two Pair
  • 3- Three of a Kind
  • 4- Straight
  • 5- Flush
  • 6- Full House
  • 7- Four of a Kind
  • 8- Straight Flush

Use the numbers associated with the hand when writing your program.

This hand can be written:

(8, 11)

Here the eight stands for straight flush and the number 11 stands for the Jack.

Here are a few examples of what a player might say and how you would write their hand:

  1. Four aces and a queen kicker?

    (7, 14, 12)

Notice in this case, and when you have one pair, two pair, three of a kind and four of a kind, that there is an extra string in the set that tells you what kind you have.

  1. Full house, eights over kings?

    (6, 8, 13)

Even though a king is higher than an eight, because there are three eights in this full house, the eight is what matters most so it is included first, followed by 13, which indicates the kings.

  1. Flush, 10-8!

Usually, declaring the two highest cards in the flush is enough to distinguish the hand from all other flushes. However, you might need all of the cards in the hand to break a tie, although it is unlikely that someone else would have the same exact hand.

When you write this, start with the highest card and work your way down.

(5, [10, 8, 7, 5, 3])
  1. Straight, Jack high

This is all you need to know, because if Jack is the high card, than the rest of the cards in the hand, since it is a straight, have to be ten, nine, eight and seven.

(4, 11)
  1. Three sevens!

Usually, this is enough information to distinguish a hand, but if you really need to break the ties, you can write the complete list of the five cards.

(3, 7, [7, 7, 7, 5, 2])
  1. Two pairs, Jacks and threes

While this describes most of the hand, you also need to compare all of the cards, including the two pairs. Write the highest ranking pair first and include a set of the entire hand, just in case there is a tie - if someone else has a hand with two pairs, Jacks and threes.

(2, 11, 3, [13, 11, 11, 3, 3])
  1. Pair of twos, Jack high

This partially describes a hand, but your player is not impressed with this hand. Here is how you represent this dismally dealt hand, make sure to include the set of the entire hand as a pair of two does not completely disambiguate this hand:

(1, 2, [11, 6, 3, 2, 2])
  1. Got nothing

Sometimes, nobody gets dealt a good hand. How do you decide who wins? Go in order of the ranks of the cards:

(0, 7, 5, 4, 3, 2)

Quiz: Poker Function

Unit1-4/

Out of the list of hands, you want poker to return the highest -ranking hand. Do you know of a built in function in Python that will allow you to return the highest-ranking item from a list?

Given:

def poker(hands):
        "return the best hand: poker([hand,...]) => hand"
        return ???

Quiz: Understanding Max

Unit1-5/

What will the two max calls return?

def poker(hands):
    "Return the best hand: poker ([hand, ...]) => hand"
    return max

Quiz: Using Max

Unit1-6/

Assume that you have defined a function hand_rank, which takes a hand as input and returns some sort of a rank. Given this, how would you write the definition of the function poker to return the maximum hand according to the highest ranked?

def poker(hands):
    "Return the best hand: poker([hand, ...]) => hand"
    return max

def hand_rank(hand):
    return ???

print max([3, 4, 5, 0]), max ([3, 4, -5, 0], key = abs)

Quiz: Testing

Unit1-7/

Modify the test() function to include two new test cases:

  • 1) four of a kind (fk) vs. full house (fh) returns fk.
  • 2) full house (fh) vs. full house (fh) returns fh.

    def poker(hands): "Return the best hand: poker([hand,...]) => hand" return max(hands, key=hand_rank)

    def test(): "Test cases for the functions in poker program" sf = "6C 7C 8C 9C TC".split() # => ['6C', '7C', '8C', '9C', 'TC'] fk = "9D 9H 9S 9C 7D".split() fh = "TD TC TH 7C 7D".split() assert poker([sf, fk, fh]) == sf assert poker([fk, fh]) == fk assert poker([fh, fh]) == fh

    # Add 2 new assert statements here. The first
    # should check that when fk plays fh, fk
    # is the winner. The second should confirm that
    # fh playing against fh returns fh.
    

    print test()

Quiz: Extreme Values

Unit1-8/

Quiz: Hand Rank Attempt

Unit1-9/

Quiz: Representing Rank

Unit1-10/

Back to Hand Rank

Unit1-12/

Quiz: Testing Hand Rank

Unit1-13/

Quiz: Writing Hand Rank

Unit1-14/

Quiz: Testing Card Rank

Unit1-15/

Quiz: Fixing Card Rank

Unit1-16/

Quiz: Straight and Flush

Unit1-17/

Quiz: Kind Function

Unit1-18/

Quiz: Two Pair Function

Unit1-19/

Quiz: Making Changes

Unit1-20/

Quiz: What to Change

Unit1-21/

Ace Low Straight

Unit1-22/

Quiz: Handling Ties

Unit1-23/

Quiz: Allmax

Unit1-24/

Deal

Unit1-25/

Quiz: Hand Frequencies

Unit1-26/

This is the procedure Peter gave us to calculate hand frequencies:

def hand_percentages(n=700*1000):
    counts = [0]*9
    for i in range(n/10):
        for hand in deal(10):
            ranking = hand_rank(hand)[0]
            counts[ranking] += 1
    for i in reversed(range(9)):
        print "%15s: %6.3f %%" % (hand_names[i], 100.*counts[i]/n)

Dimensions of Programming

Unit1-27/

Refactoring

Unit1-28/

The two alternative versions of hand_rank that Peter gave in the refactoring class are:

def hand_rank_alt(hand):
    "Return a value indicating how high the hand ranks."
    # count is the count of each rank; ranks lists corresponding ranks
    # E.g. '7 T 7 9 7' => counts = (3, 1, 1) ranks = (7, 10, 9)
    groups = group(['--23456789TJQKA'.index(r) for r,s in hand])
    counts, ranks = unzip(groups)
    if ranks == (14, 5, 4, 3, 2):   # Ace low straight
        ranks = (5, 4, 3, 2, 1)
    straight = len(ranks) == 5 and max(ranks) - min(ranks) == 4
    flush = len(set([s for r,s in hand])) == 1
    return (9 if (5,) == counts else
            8 if straight and flush else
            7 if (4, 1) == counts else
            6 if (3, 2) == counts else
            5 if flush else
            4 if straight else
            3 if (3, 1, 1) == counts else
            2 if (2, 2, 1) == counts else
            1 if (2, 1, 1, 1) == counts else
            0), ranks

def group(items):
    "Return a list of [(count, x), ...], highest count first, then highest x first"
    groups = [(items.count(x), x) for x in set(items)]
    return sorted(groups, reverse = True)

def unzip(iterable):
    "Return a tuple of lists from a list of tuples : e.g. [(2, 9), (2, 7)] => ([2, 2], [9, 7])"
    return zip(*iterable)

The table-based lookup version:

count_rankings = {(5,): 10, (4, 1): 7, (3, 2): 6, (3, 1, 1): 3, (2, 2, 1): 2,
                  (2, 1, 1, 1): 1, (1, 1, 1, 1, 1): 0}

def hand_rank_table(hand):
    "Return a value indicating how high the hand ranks."
    # count is the count of each rank; ranks lists corresponding ranks
    # E.g. '7 T 7 9 7' => counts = (3, 1, 1) ranks = (7, 10, 9)
    groups = group(['--23456789TJQKA'.index(r) for r,s in hand])
    counts, ranks = unzip(groups)
    if ranks == (14, 5, 4, 3, 2):   # Ace low straight
        ranks = (5, 4, 3, 2, 1)
    straight = len(ranks) == 5 and max(ranks) - min(ranks) == 4
    flush = len(set([s for r,s in hand])) == 1
    return max(count_rankings[counts], 4*straight + 5*flush), ranks

Summary

Unit1-29/

Bonus - Shuffling

The shuffling procedures from the bonus videos are:

def shuffle1(p):
    n = len(p)
    swapped = [False]*n
    while not all(swapped):
        i, j = random.randrange(n), random.randrange(n)
        swap(p, i, j)
        swapped[i] = swapped[j] = True

def shuffle2(p):
    n = len(p)
    swapped = [False]*n
    while not all(swapped):
        i, j = random.randrange(n), random.randrange(n)
        swap(p, i, j)
        swapped[i] = True

def shuffle3(p):
    n = len(p)
    for i in range(n):
        swap(p, i, random.randrange(n))

def knuth(p):
    n = len(p)
    for i in range(n-1):
        swap(p, i, random.randrange(i, n))
def swap(p, i, j):
    p[i], p[j] = p[j], p[i]

The procedures for testing the different shuffles were:

def test_shuffle(shuffler, deck = 'abcd', n = 10000):
    counts = defaultdict(int)
    for _ in range(n):
        input = list(deck)
        shuffler(input)
        counts[''.join(input)] += 1
    e = n * 1./factorial(len(deck))
    ok = all((0.9 <= counts[item]/e <= 1.1) for item in counts)
    name = shuffler.__name__
    print '%s(%s) %s' % (name,  deck, ('ok' if ok else '*** BAD ***'))
    print '   ',
    for item, count in sorted(counts.items()):
        print "%s:%4.1f" % (item, count * 100. / n),
    print

def test_shufflers(shufflers = [knuth, shuffle1, shuffle2, shuffle3], decks = ['abc', 'ab']):
    for deck in decks:
        print
        for f in shufflers:
            test_shuffle(f, deck)
def factorial(n):
    return 1 if n<= 1 else n * factorial(n-1)

Complete Code For Poker Problem

The complete code given by Peter in this unit including some additional test cases:

#! /usr/bin/env python

import random

def poker(hands):
    "Return a list of winning hands: poker([hand,...]) => [hand,...]"
    return allmax(hands, key=hand_rank)

def allmax(iterable, key=None):
    "Return a list of all items equal to the max of the iterable."
    iterable.sort(key=key,reverse=True)
    result = [iterable[0]]
    maxValue = key(iterable[0]) if key else iterable[0]
    for value in iterable[1:]:
        v = key(value) if key else value
        if v == maxValue: result.append(value)
        else: break
    return result

def card_ranks(hand):
    "Return a list of the ranks, sorted with higher first."
    ranks = ['--23456789TJQKA'.index(r) for r, s in hand]
    ranks.sort(reverse = True)
    return [5, 4, 3, 2, 1] if (ranks == [14, 5, 4, 3, 2]) else ranks

def flush(hand):
    "Return True if all the cards have the same suit."
    suits = [s for r,s in hand]
    return len(set(suits)) == 1

def straight(ranks):
    "Return True if the ordered ranks form a 5-card straight."
    return (max(ranks)-min(ranks) == 4) and len(set(ranks)) == 5

def kind(n, ranks):
    """Return the first rank that this hand has exactly n-of-a-kind of.
    Return None if there is no n-of-a-kind in the hand."""
    for r in ranks:
        if ranks.count(r) == n: return r
    return None

def two_pair(ranks):
    "If there are two pair here, return the two ranks of the two pairs, else None."
    pair = kind(2, ranks)
    lowpair = kind(2, list(reversed(ranks)))
    if pair and lowpair != pair:
        return (pair, lowpair)
    else:
        return None

def hand_rank(hand):
    "Return a value indicating the ranking of a hand."
    ranks = card_ranks(hand)
    if straight(ranks) and flush(hand):
        return (8, max(ranks))
    elif kind(4, ranks):
        return (7, kind(4, ranks), kind(1, ranks))
    elif kind(3, ranks) and kind(2, ranks):
        return (6, kind(3, ranks), kind(2, ranks))
    elif flush(hand):
        return (5, ranks)
    elif straight(ranks):
        return (4, max(ranks))
    elif kind(3, ranks):
        return (3, kind(3, ranks), ranks)
    elif two_pair(ranks):
        return (2, two_pair(ranks), ranks)
    elif kind(2, ranks):
        return (1, kind(2, ranks), ranks)
    else:
        return (0, ranks)

def hand_rank_alt(hand):
    "Return a value indicating how high the hand ranks."
    # count is the count of each rank; ranks lists corresponding ranks
    # E.g. '7 T 7 9 7' => counts = (3, 1, 1) ranks = (7, 10, 9)
    groups = group(['--23456789TJQKA'.index(r) for r,s in hand])
    counts, ranks = unzip(groups)
    if ranks == (14, 5, 4, 3, 2):   # Ace low straight
        ranks = (5, 4, 3, 2, 1)
    straight = len(ranks) == 5 and max(ranks) - min(ranks) == 4
    flush = len(set([s for r,s in hand])) == 1
    return (9 if (5,) == counts else
            8 if straight and flush else
            7 if (4, 1) == counts else
            6 if (3, 2) == counts else
            5 if flush else
            4 if straight else
            3 if (3, 1, 1) == counts else
            2 if (2, 2, 1) == counts else
            1 if (2, 1, 1, 1) == counts else
            0), ranks

count_rankings = {(5,): 10, (4, 1): 7, (3, 2): 6, (3, 1, 1): 3, (2, 2, 1): 2,
                  (2, 1, 1, 1): 1, (1, 1, 1, 1, 1): 0}

def hand_rank_table(hand):
    "Return a value indicating how high the hand ranks."
    # count is the count of each rank; ranks lists corresponding ranks
    # E.g. '7 T 7 9 7' => counts = (3, 1, 1) ranks = (7, 10, 9)
    groups = group(['--23456789TJQKA'.index(r) for r,s in hand])
    counts, ranks = unzip(groups)
    if ranks == (14, 5, 4, 3, 2):   # Ace low straight
        ranks = (5, 4, 3, 2, 1)
    straight = len(ranks) == 5 and max(ranks) - min(ranks) == 4
    flush = len(set([s for r,s in hand])) == 1
    return max(count_rankings[counts], 4*straight + 5*flush), ranks

def group(items):
    "Return a list of [(count, x), ...], highest count first, then highest x first"
    groups = [(items.count(x), x) for x in set(items)]
    return sorted(groups, reverse = True)

def unzip(iterable):
    "Return a list of tuples from a list of tuples : e.g. [(2, 9), (2, 7)] => [(2, 2), (9, 7)]"
    return zip(*iterable)

mydeck = [r+s for r in '23456789TJQKA' for s in 'SHDC']

def deal(numhands, n=5, deck=mydeck):
    random.shuffle(mydeck)
    return [mydeck[n*i:n*(i+1)] for i in range(numhands)]

hand_names = ["Straight flush", "Four of a kind", "Full house", "Flush", "Straight",
              "Three of a kind", "Two pair", "One pair", "High card"]

def hand_percentages(n=700*1000):
    counts = [0]*9
    for i in range(n/10):
        for hand in deal(10):
            ranking = hand_rank(hand)[0]
            counts[ranking] += 1
    for i in reversed(range(9)):
        print "%15s: %6.3f %%" % (hand_names[i], 100.*counts[i]/n)

def test():
    "Test cases for the functions in poker program."
    sf1 = "6C 7C 8C 9C TC".split() # Straight Flush
    sf2 = "6D 7D 8D 9D TD".split() # Straight Flush
    fk = "9D 9H 9S 9C 7D".split() # Four of a Kind
    fh = "TD TC TH 7C 7D".split() # Full House
    tp = "5D 2C 2H 9H 5C".split() # Two Pair

    # Testing allmax
    assert allmax([2,4,7,5,1]) == [7]
    assert allmax([2,4,7,5,7]) == [7,7]
    assert allmax([2]) == [2]
    assert allmax([0,0,0]) == [0,0,0]

    # Testing card_ranks
    assert card_ranks(sf1) == [10, 9, 8, 7, 6]
    assert card_ranks(fk) == [9, 9, 9, 9, 7]
    assert card_ranks(fh) == [10, 10, 10, 7, 7]

    # Testing flush
    assert flush([]) == False
    assert flush(sf1) == True
    assert flush(fh) == False

    # Testing straight
    assert straight(card_ranks(sf1)) == True
    assert straight(card_ranks(fk)) == False

    # Testing kind
    assert kind(3, card_ranks(sf1)) == None
    assert kind(4, card_ranks(fk)) == 9

    # Tesing two pair
    assert two_pair(card_ranks(sf1)) == None
    assert two_pair(card_ranks(tp)) == (5,2)

    # Testing group
    assert group([2,3,4,6,2,1,9]) == [(2,2),(1,9),(1,6),(1,4),(1,3),(1,1)]
    assert group([8,8,8,8]) == [(4,8)]
    assert group([2,6,1]) == [(1,6),(1,2),(1,1)]

    # Testing unzip
    assert unzip([(2,2),(1,9),(1,6),(1,4),(1,3),(1,1)]) == [(2,1,1,1,1,1),(2,9,6,4,3,1)]
    assert unzip([(1,6),(1,2),(1,1)]) == [(1,1,1),(6,2,1)]
    assert unzip([(2, 9), (2, 7)]) == [(2, 2), (9, 7)]

    # Testing hand rank
    assert hand_rank(sf1) == (8,10)
    assert hand_rank(fk) == (7,9,7)
    assert hand_rank(fh) == (6,10,7)

    # Testing hand rank alt
    assert hand_rank_alt(sf1) == (8, (10,9,8,7,6))
    assert hand_rank_alt(fk) == (7,(9,7))
    assert hand_rank_alt(fh) == (6,(10,7))

    # Testing hand rank table
    assert hand_rank_table(sf1) == (9, (10,9,8,7,6))
    assert hand_rank_table(fk) == (7,(9,7))
    assert hand_rank_table(fh) == (6,(10,7))

    # Testing poker
    assert poker([sf1, fk, fh]) == [sf1]
    assert poker([fk, fh]) == [fk]
    assert poker([fh, fh]) == [fh, fh]
    assert poker([fh]) == [fh]
    assert poker([sf2] + 99*[fh]) == [sf2]
    assert poker([sf1, sf2, fk, fh]) == [sf1, sf2]

    return 'tests pass'

Complete Code For Homeworks (Warning: Refer this only after submitting homework)

The complete homework solution code given by Peter in this unit.

Homework 1:

# CS 212, hw1-1: 7-card stud
#
# -----------------
# User Instructions
#
# Write a function best_hand(hand) that takes a seven
# card hand as input and returns the best possible 5
# card hand. The itertools library has some functions
# that may help you solve this problem.
#
# -----------------
# Grading Notes
#
# Muliple correct answers will be accepted in cases
# where the best hand is ambiguous (for example, if
# you have 4 kings and 3 queens, there are three best
# hands: 4 kings along with any of the three queens).

import itertools

def best_hand(hand):
    "From a 7-card hand, return the best 5 card hand."
    return max(itertools.combinations(hand, 5), key=hand_rank)

# ------------------
# Provided Functions
#
# You may want to use some of the functions which
# you have already defined in the unit to write
# your best_hand function.

def hand_rank(hand):
    "Return a value indicating the ranking of a hand."
    ranks = card_ranks(hand)
    if straight(ranks) and flush(hand):
        return (8, max(ranks))
    elif kind(4, ranks):
        return (7, kind(4, ranks), kind(1, ranks))
    elif kind(3, ranks) and kind(2, ranks):
        return (6, kind(3, ranks), kind(2, ranks))
    elif flush(hand):
        return (5, ranks)
    elif straight(ranks):
        return (4, max(ranks))
    elif kind(3, ranks):
        return (3, kind(3, ranks), ranks)
    elif two_pair(ranks):
        return (2, two_pair(ranks), ranks)
    elif kind(2, ranks):
        return (1, kind(2, ranks), ranks)
    else:
        return (0, ranks)

def card_ranks(hand):
    "Return a list of the ranks, sorted with higher first."
    ranks = ['--23456789TJQKA'.index(r) for r, s in hand]
    ranks.sort(reverse = True)
    return [5, 4, 3, 2, 1] if (ranks == [14, 5, 4, 3, 2]) else ranks

def flush(hand):
    "Return True if all the cards have the same suit."
    suits = [s for r,s in hand]
    return len(set(suits)) == 1

def straight(ranks):
    """Return True if the ordered
    ranks form a 5-card straight."""
    return (max(ranks)-min(ranks) == 4) and len(set(ranks)) == 5

def kind(n, ranks):
    """Return the first rank that this hand has
    exactly n-of-a-kind of. Return None if there
    is no n-of-a-kind in the hand."""
    for r in ranks:
        if ranks.count(r) == n: return r
    return None

def two_pair(ranks):
    """If there are two pair here, return the two
    ranks of the two pairs, else None."""
    pair = kind(2, ranks)
    lowpair = kind(2, list(reversed(ranks)))
    if pair and lowpair != pair:
        return (pair, lowpair)
    else:
        return None

def test_best_hand():
    assert (sorted(best_hand("6C 7C 8C 9C TC 5C JS".split()))
            == ['6C', '7C', '8C', '9C', 'TC'])
    assert (sorted(best_hand("TD TC TH 7C 7D 8C 8S".split()))
            == ['8C', '8S', 'TC', 'TD', 'TH'])
    assert (sorted(best_hand("JD TC TH 7C 7D 7S 7H".split()))
            == ['7C', '7D', '7H', '7S', 'JD'])
    return 'test_best_hand passes'

print test_best_hand()

Homework 2:

# CS 212, hw1-2: Jokers Wild
#
# -----------------
# User Instructions
#
# Write a function best_wild_hand(hand) that takes as
# input a 7-card hand and returns the best 5 card hand.
# In this problem, it is possible for a hand to include
# jokers. Jokers will be treated as 'wild cards' which
# can take any rank or suit of the same color. The
# black joker, '?B', can be used as any spade or club
# and the red joker, '?R', can be used as any heart
# or diamond.
#
# The itertools library may be helpful. Feel free to
# define multiple functions if it helps you solve the
# problem.
#
# -----------------
# Grading Notes
#
# Muliple correct answers will be accepted in cases
# where the best hand is ambiguous (for example, if
# you have 4 kings and 3 queens, there are three best
# hands: 4 kings along with any of the three queens).

import itertools

## Deck adds two cards:
## '?B': black joker; can be used as any black card (S or C)
## '?R': red joker; can be used as any red card (H or D)

allranks = '23456789TJQKA'
redcards = [r+s for r in allranks for s in 'DH']
blackcards = [r+s for r in allranks for s in 'SC']

def best_wild_hand(hand):
    "Try all values for jokers in all 5-card selections."
    hands = set(best_hand(h)
                for h in itertools.product(*map(replacements, hand)))
    return max(hands, key=hand_rank)

def replacements(card):
    """Return a list of the possible replacements for a card.
    There will be more than 1 only for wild cards."""
    if card == '?B': return blackcards
    elif card == '?R': return redcards
    else: return [card]

def best_hand(hand):
    "From a 7-card hand, return the best 5 card hand."
    return max(itertools.combinations(hand, 5), key=hand_rank)

def test_best_wild_hand():
    assert (sorted(best_wild_hand("6C 7C 8C 9C TC 5C ?B".split()))
            == ['7C', '8C', '9C', 'JC', 'TC'])
    assert (sorted(best_wild_hand("TD TC 5H 5C 7C ?R ?B".split()))
            == ['7C', 'TC', 'TD', 'TH', 'TS'])
    assert (sorted(best_wild_hand("JD TC TH 7C 7D 7S 7H".split()))
            == ['7C', '7D', '7H', '7S', 'JD'])
    return 'test_best_wild_hand passes'

# ------------------
# Provided Functions
#
# You may want to use some of the functions which
# you have already defined in the unit to write
# your best_hand function.

def hand_rank(hand):
    "Return a value indicating the ranking of a hand."
    ranks = card_ranks(hand)
    if straight(ranks) and flush(hand):
        return (8, max(ranks))
    elif kind(4, ranks):
        return (7, kind(4, ranks), kind(1, ranks))
    elif kind(3, ranks) and kind(2, ranks):
        return (6, kind(3, ranks), kind(2, ranks))
    elif flush(hand):
        return (5, ranks)
    elif straight(ranks):
        return (4, max(ranks))
    elif kind(3, ranks):
        return (3, kind(3, ranks), ranks)
    elif two_pair(ranks):
        return (2, two_pair(ranks), ranks)
    elif kind(2, ranks):
        return (1, kind(2, ranks), ranks)
    else:
        return (0, ranks)

def card_ranks(hand):
    "Return a list of the ranks, sorted with higher first."
    ranks = ['--23456789TJQKA'.index(r) for r, s in hand]
    ranks.sort(reverse = True)
    return [5, 4, 3, 2, 1] if (ranks == [14, 5, 4, 3, 2]) else ranks

def flush(hand):
    "Return True if all the cards have the same suit."
    suits = [s for r,s in hand]
    return len(set(suits)) == 1

def straight(ranks):
    """Return True if the ordered
    ranks form a 5-card straight."""
    return (max(ranks)-min(ranks) == 4) and len(set(ranks)) == 5

def kind(n, ranks):
    """Return the first rank that this hand has
    exactly n-of-a-kind of. Return None if there
    is no n-of-a-kind in the hand."""
    for r in ranks:
        if ranks.count(r) == n: return r
    return None

def two_pair(ranks):
    """If there are two pair here, return the two
    ranks of the two pairs, else None."""
    pair = kind(2, ranks)
    lowpair = kind(2, list(reversed(ranks)))
    if pair and lowpair != pair:
        return (pair, lowpair)
    else:
        return None

print test_best_wild_hand()