Midterm 1, Spring 2024: Ranking Poker Hands

Version 1.0.1

History:

  • 1.0.1: Fix typo.
  • 1.0.0: Initial release.

All of the header information is important. Please read it..

Topics, number of exercises: This problem builds on your knowledge of conditional logic, implementing math as code, and Python's representation of numbers. It has 8 exercises, numbered 0 to 7. There are 15 available points. However, to earn 100% the threshold is 10 points. (Therefore, once you hit 10 points, you can stop. There is no extra credit for exceeding this threshold.)

Exercise ordering: Each exercise builds logically on previous exercises, but you may solve them in any order. That is, if you can't solve an exercise, you can still move on and try the next one. Use this to your advantage, as the exercises are not necessarily ordered in terms of difficulty. Higher point values indicate more complicated exercises which may be more time consuming to solve.

Demo cells: Code cells starting with the comment ### define demo inputs load results from prior exercises applied to the entire data set and use those to build demo inputs. These must be run for subsequent demos to work properly, but they do not affect the test cells. The data loaded in these cells may be rather large (at least in terms of human readability). You are free to print or otherwise use Python to explore them, but we did not print them in the starter code.

Debugging you code: Right before each exercise test cell, there is a block of text explaining the variables available to you for debugging. You may use these to test your code and can print/display them as needed (careful when printing large objects, you may want to print the head or chunks of rows at a time).

Exercise point breakdown:

  • Exercise 0: 1 point(s)
  • Exercise 1: 1 point(s)
  • Exercise 2: 2 point(s)
  • Exercise 3: 1 point(s)
  • Exercise 4: 3 point(s)
  • Exercise 5: 3 point(s)
  • Exercise 6: 2 point(s)
  • Exercise 7: 2 point(s)

Final reminders:

  • Submit after every exercise
  • Review the generated grade report after you submit to see what errors were returned
  • Stay calm, skip problems as needed, and take short breaks at your leisure

Topic Introduction

The goal of this notebook is to write a computer program which can determine the relative strength of poker hands. Inspiration was drawn from this blog where the author walks through building a similar program in the C programming language.

In [ ]:
### Global Imports
### BEGIN HIDDEN TESTS
if False: # set to True to set up
    import dill
    import hashlib
    def hash_check(f1, f2, verbose=True):
        with open(f1, 'rb') as f:
            h1 = hashlib.md5(f.read()).hexdigest()
        with open(f2, 'rb') as f:
            h2 = hashlib.md5(f.read()).hexdigest()
        if verbose:
            print(h1)
            print(h2)
        assert h1 == h2, f'The file "{f1}" has been modified'
    with open('resource/asnlib/public/hash_check.pkl', 'wb') as f:
        dill.dump(hash_check, f)
    del hash_check
    with open('resource/asnlib/public/hash_check.pkl', 'rb') as f:
        hash_check = dill.load(f)
    for fname in ['testers.py', '__init__.py', 'test_utils.py']:
        hash_check(f'tester_fw/{fname}', f'resource/asnlib/public/{fname}')
    del hash_check
### END HIDDEN TESTS
from poker_game import \
    poker_info, \
    cards_info, \
    pprint_cards, \
    ranks_info, \
    pprint_ranks
    
from pprint import pprint

Exercise 0 - (1 Points):

The first exercise is to read this background information on poker, the data structures we will use, and some utilities we have built to make understanding the data simpler.

Representing cards

Playing cards are traditionally pieces of cardstock material which are marked with a rank and a suit. A deck of cards contains all possible combinations of ranks and suits. There are 13 ranks and 4 suits for a total of 52 cards. The image below shows the suits as rows and the ranks as columns.

resource/asnlib/publicdata/cards.png

Since we can't hand out pyhsical cards we're going to have to come up with some way of representing collections of cards (i.e. subsets of a deck) in Python. This notebook relies on two in particular:

  • String representation: This is a human-readable representation, but it is not very handy for calculation. Each card in a collection is a two-character sequence identifying its rank and suit. The collection is space-delimited. For example: 'TH AH QS 9D JD'
    • suit: single character in 'HSCD'
    • rank: single character in 'AKQJT98765432'
  • Integer representation: This representation is much less human-readable, but it is very efficient for calculation. The idea here is we create a mapping of the 52 cards to 52 bit-positions. Each bit is 1 if the mapped card is present in the collection and 0 otherwise.
    • example:
      • 351843729281280 - This is the integer version of 'TH AH QS 9D JD'
      • bin(351843729281280) -> '0b1010000000000000000000000100000000001000100000000'
    • For mapping bit positions to suit/rank combinations we use the convention of breaking the 52-bit representation into four groups of 13.
      • Each group is assigned a suit.
      • From right to left in each group the bits are assigned to ranks in ascending order of strength.
        • (i.e. the right-most bit is a '2' and the left-most bit is an 'A').
    • To provide more visibility into this representation we have provided some functions:
      • pprint_cards(cards) prints the binary representation of cards annotated with the suit and rank mapped to each bit position:
          pprint_cards(351843729281280) prints:
          SUIT: DDDDDDDDDDDDD CCCCCCCCCCCCC SSSSSSSSSSSSS HHHHHHHHHHHHH
          RANK: AKQJT98765432 AKQJT98765432 AKQJT98765432 AKQJT98765432
          BITS: 0001010000000 0000000000000 0010000000000 1000100000000
      • cards_info(cards) returns a dictionary with some summary information about cards
          cards_info(351843729281280) -> 
          {'binary_representation': '0b0001010000000000000000000000100000000001000100000000',
           'int_value': 351843729281280,
           'string_representation': 'TH AH QS 9D JD',
           'suit_groups': {'C': 0, 'D': 640, 'H': 4352, 'S': 1024}}
    • The suit_groups values in the cards_info output are the integer representation of the subset of ranks which make up the suit groups (13-bit sequence sharing the same suit). In general we can represent any subset of the possible ranks with a 13-bit integer. We have provided functions for visibility into these representations as well.
      • pprint_ranks(ranks) prints the binary representation of ranks annotated with the rank mapped to each bit position.
          pprint_ranks(640) prints:
          RANK: AKQJT98765432 
          BITS: 0001010000000
      • ranks_info(ranks) returns a dictionary with some summary information about ranks.
          ranks_info(640) ->
          {'int_value': 640, 'binary_representation': '0b0001010000000', 'string_representation': '9 J'}

You can view the source code in the poker_game module (download file)

Ranking hands

A poker hand is a collection of 5 cards. A hand has three attributes which determine its strength relative to other hands:

  • hand type: category assigned to the hand based on criteria related to the ranks and suits in it. Types which are less likely to occur are considered stronger.
  • primary ranks: the ranks in the "made" part of the hand
    • i.e. for "three-of-a-kind" the primary rank is the rank which appears 3 times in the hand
  • secondary ranks: the ranks which are not in the "made" part of the hand
    • i.e. for "three-of-a-kind" the secondary ranks are the two ranks which appear once in the hand.
    • there may be no secondary ranks - which would be a blank string or 0 depending on the representation.

Additionally we have created a dictionary containing useful constants called poker_info. You can view the exact contents and structure in poker_info.py (download file). Here's a high-level description of the contents:

  • RANKS: dictionary mapping the string representation of each rank to its bit position in the 13-bit integer representation.
    • The values are also the relative strengths of the ranks. Higher number means higher strength.
  • SUITS: dictionary mapping the string representation of each suit to an attributes dictionary.
    • Each attributes dictionary contains the index, symbol, start/end position and the bit-mask for the bit positions associated with the suit in the 52-bit representation of cards.
  • CARDS: dictionary mapping the string representation of each card to the 52-bit integer representation of the card.
  • HAND_TYPES: dictionary mapping the types of poker hands to the relative strength of the type.
  • FIVE_HIGH: 13-bit integer representation of the ranks present in a special case which is an exception to the normal rules for determining the type of a poker hand.
In [ ]:
### test_cell_ex0
### BEGIN HIDDEN TESTS
import dill
import hashlib
with open('resource/asnlib/public/hash_check.pkl', 'rb') as f:
    hash_check = dill.load(f)
for fname in ['testers.py', '__init__.py', 'test_utils.py']:
    hash_check(f'tester_fw/{fname}', f'resource/asnlib/public/{fname}')
del hash_check
del dill
del hashlib
### END HIDDEN TESTS

print('Passed! Please submit.')

Exercise 1 - (1 Points):

For the first few exercises we will make some more utilities that will be useful in completing the later exercises.

We have functionality to go from the integer representation to the string representation of a collection of cards. To close the loop on user interaction we must create a utility to convert the string representation of cards to the 52-bit integer representation.

Define hand_from_str(s: str)->int: as follows:

  • Input:
    • s: string representation of a collection of cards as described above. The processing of s is not case sensitive, so inputs can be upper or lower case.
  • Behavior:
    • Split s by whitespace and convert to uppercase
    • Use the mapping in poker_info to determine the integer representation of each card
  • Output:
    • The sum of the integer representations of all the cards in the collection according to s

Recall from the introductory material that the dictionary poker_info has a lot of useful constants and that the functions cards_info, pprint_cards, ranks_info and pprint_ranks can be used to get more information about the integer representations of collections of cards or ranks.

In [ ]:
### Define demo inputs

demo_s_ex_0 = 'ah TH jd qs 9D'
The demo included in the solution cell below should display the following output: ``` integer returned: 351843729281280 pretty printed: SUIT: DDDDDDDDDDDDD CCCCCCCCCCCCC SSSSSSSSSSSSS HHHHHHHHHHHHH RANK: AKQJT98765432 AKQJT98765432 AKQJT98765432 AKQJT98765432 BITS: 0001010000000 0000000000000 0010000000000 1000100000000 ```
In [ ]:
### Exercise 1 solution
def hand_from_str(s: str) -> int:
    ### BEGIN SOLUTION
    return sum(poker_info['CARDS'][_s] for _s in s.upper().split())  
    ### END SOLUTION
    
### demo function call
# call the function defined above using the demo inputs.
print('integer returned:')
print(hand_from_str(demo_s_ex_0))
print()
print('pretty printed:')
pprint_cards(hand_from_str(demo_s_ex_0))

The cell below will test your solution for Exercise 1. The testing variables will be available for debugging under the following names in a dictionary format.

  • input_vars - Input variables for your solution.
  • original_input_vars - Copy of input variables from prior to running your solution. These should be the same as input_vars - otherwise the inputs were modified by your solution.
  • returned_output_vars - Outputs returned by your solution.
  • true_output_vars - The expected output. This should "match" returned_output_vars based on the question requirements - otherwise, your solution is not returning the correct output.
In [ ]:
### test_cell_ex1
### BEGIN HIDDEN TESTS
import dill
import hashlib
with open('resource/asnlib/public/hash_check.pkl', 'rb') as f:
    hash_check = dill.load(f)
for fname in ['testers.py', '__init__.py', 'test_utils.py']:
    hash_check(f'tester_fw/{fname}', f'resource/asnlib/public/{fname}')
del hash_check
del dill
del hashlib
### END HIDDEN TESTS
from tester_fw.testers import Tester

conf = {
    'case_file':'tc_1', 
    'func': hand_from_str, # replace this with the function defined above
    'inputs':{ # input config dict. keys are parameter names
        's':{
            'dtype':'str', # data type of param.
            'check_modified':False,
        }
    },
    'outputs':{
        'output_0':{
            'index':0,
            'dtype':'int',
            'check_dtype': True,
            'check_col_dtypes': True, # Ignored if dtype is not df
            'check_col_order': True, # Ignored if dtype is not df
            'check_row_order': True, # Ignored if dtype is not df
            'check_column_type': True, # Ignored if dtype is not df
            'float_tolerance': 10 ** (-6)
        }
    }
}
tester = Tester(conf, key=b'kvX8VF1YUgURm8NySX8Cx_UW0ZHCC6WCFqotGOpgvQc=', path='resource/asnlib/publicdata/')
for _ in range(70):
    try:
        tester.run_test()
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
    except:
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
        raise

### BEGIN HIDDEN TESTS
tester = Tester(conf, key=b'RKLlutzjcNM-oSSPrBDwxq7ABnEKW0UkWDk2jr60_t4=', path='resource/asnlib/publicdata/encrypted/')
for _ in range(20):
    try:
        tester.run_test()
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
    except:
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
        raise
### END HIDDEN TESTS
print('Passed! Please submit.')

Exercise 2 - (2 Points):

We have a few mappings from the string to integer representations, but having the reverse mappings will also be useful. We want to make sure that the reverse mappings are valid

  • a reverse mapping is valid if it has the same number of keys as the original mapping.
  • If that's not the case then there are duplicate values in the original and Python re-assigned one of them. It's not deterministic which original key gets over-written.

Define reverse_dict(d: dict) -> dict as follows:

  • Input:
    • d: a Python dictionary
  • Behavior:
    • build a new dictionary d_rev that swaps the key/value pairs.
      • d[d_rev[v]] is equal to v for any v which is a value in d
      • d_rev[d[k]] is equal to k for any k which is a key in d
    • if there are duplicate values in d raise a ValueError
  • Output:
    • d_rev as described above
In [ ]:
### Define demo inputs

demo_d_ex2 = {'x': 1, 'y': 2, 'kangaroo': 'llama'}
demo_d_invalid_ex2 = {'x': 1, 'y': 2, 'kangaroo': 2}
The demo included in the solution cell below should display the following output: ``` reversed {'x': 1, 'y': 2, 'kangaroo': 'llama'} into {1: 'x', 2: 'y', 'llama': 'kangaroo'} A ValueError was raised. Are there duplicate values in {'x': 1, 'y': 2, 'kangaroo': 2}? ```
In [ ]:
### Exercise 2 solution

def reverse_dict(d: dict) -> dict:
    ### BEGIN SOLUTION
    result = {v:k for k, v in d.items()}
    if len(result) != len(d):
        raise ValueError()
    return result
    ### END SOLUTION

# demo
for d in [demo_d_ex2, demo_d_invalid_ex2]:
    try:
        d_rev = reverse_dict(d)
        print(f'Successfully reversed {d} into {d_rev}')
    except ValueError:
        print(f'A ValueError was raised. Are there duplicate values in {d}?')

Since part of the requirements are for your function to raise an error we will wrap your function with a decorator for testing. The decorated function will give the output of your function and an indicator of whether the error was raised. For example, (1234, False) means the output is 1234 and no ValueError was raised. (None, True) means that an error was raised and no output was returned.

The cell below will test your solution for Exercise 2. The testing variables will be available for debugging under the following names in a dictionary format.

  • input_vars - Input variables for your solution.
  • original_input_vars - Copy of input variables from prior to running your solution. These should be the same as input_vars - otherwise the inputs were modified by your solution.
  • returned_output_vars - Outputs returned by your solution.
  • true_output_vars - The expected output. This should "match" returned_output_vars based on the question requirements - otherwise, your solution is not returning the correct output.
In [ ]:
### test_cell_ex2
### BEGIN HIDDEN TESTS
import dill
import hashlib
with open('resource/asnlib/public/hash_check.pkl', 'rb') as f:
    hash_check = dill.load(f)
for fname in ['testers.py', '__init__.py', 'test_utils.py']:
    hash_check(f'tester_fw/{fname}', f'resource/asnlib/public/{fname}')
del hash_check
del dill
del hashlib
### END HIDDEN TESTS
from tester_fw.testers import Tester

def ex2_wrapper(d):
    val = None
    err = False
    try:
        val = reverse_dict(d)
    except ValueError:
        err = True
    return (val, err)
    

conf = {
    'case_file':'tc_2', 
    'func': ex2_wrapper, # replace this with the function defined above
    'inputs':{ # input config dict. keys are parameter names
        'd':{
            'dtype':'dict', # data type of param.
            'check_modified':True,
        }
    },
    'outputs':{
        'output_0':{
            'index':0,
            'dtype':'',
            'check_dtype': False,
            'check_col_dtypes': False, # Ignored if dtype is not df
            'check_col_order': True, # Ignored if dtype is not df
            'check_row_order': True, # Ignored if dtype is not df
            'check_column_type': True, # Ignored if dtype is not df
            'float_tolerance': 0
        },
        'output_1':{
            'index':1,
            'dtype':'bool',
            'check_dtype': True,
            'check_col_dtypes': True, # Ignored if dtype is not df
            'check_col_order': True, # Ignored if dtype is not df
            'check_row_order': True, # Ignored if dtype is not df
            'check_column_type': True, # Ignored if dtype is not df
            'float_tolerance': 0
        }
    }
}
tester = Tester(conf, key=b'kvX8VF1YUgURm8NySX8Cx_UW0ZHCC6WCFqotGOpgvQc=', path='resource/asnlib/publicdata/')
for _ in range(70):
    try:
        tester.run_test()
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
    except:
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
        raise

### BEGIN HIDDEN TESTS
tester = Tester(conf, key=b'RKLlutzjcNM-oSSPrBDwxq7ABnEKW0UkWDk2jr60_t4=', path='resource/asnlib/publicdata/encrypted/')
for _ in range(20):
    try:
        tester.run_test()
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
    except:
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
        raise
### END HIDDEN TESTS
print('Passed! Please submit.')

Exercise 3 - (1 Points):

It's hard to tell how many cards/ranks appear in the 52/13-bit integer representations. We need a way to count the bits which are equal to 1 in an integer.

Define count_bits(n: int) -> int as follows:

  • Input:
    • n: a Python integer
  • Behavior:
    • Convert n to a binary representation
    • Count the number of times 1 appears in that representation
    • Note int.bit_count() is not implemented in Python 3.8
  • Output:
    • The count of ones in the binary representation
In [ ]:
### Define demo inputs

# Note: Python will treat a sequence of 1s and 0s prefixed by "0b" as an integer
#       The value will be what's represented in binary by the 1s and 0s
#       The line below is equivalent to `demo_n_ex3 = 1941`

demo_n_ex3 = 0b11110010101 # equivalent to `demo_n_ex3 = 1941`
The demo included in the solution cell below should display the following output: ``` 7 ```
In [ ]:
### Exercise 3 solution
def count_bits(n: int) -> int:
    ### BEGIN SOLUTION
    return bin(n).count('1')
    ### END SOLUTION
    
### demo function call
count_bits(demo_n_ex3)

The cell below will test your solution for Exercise 3. The testing variables will be available for debugging under the following names in a dictionary format.

  • input_vars - Input variables for your solution.
  • original_input_vars - Copy of input variables from prior to running your solution. These should be the same as input_vars - otherwise the inputs were modified by your solution.
  • returned_output_vars - Outputs returned by your solution.
  • true_output_vars - The expected output. This should "match" returned_output_vars based on the question requirements - otherwise, your solution is not returning the correct output.
In [ ]:
### test_cell_ex3
### BEGIN HIDDEN TESTS
import dill
import hashlib
with open('resource/asnlib/public/hash_check.pkl', 'rb') as f:
    hash_check = dill.load(f)
for fname in ['testers.py', '__init__.py', 'test_utils.py']:
    hash_check(f'tester_fw/{fname}', f'resource/asnlib/public/{fname}')
del hash_check
del dill
del hashlib
### END HIDDEN TESTS
from tester_fw.testers import Tester

conf = {
    'case_file':'tc_3', 
    'func': count_bits, # replace this with the function defined above
    'inputs':{ # input config dict. keys are parameter names
        'n':{
            'dtype':'int', # data type of param.
            'check_modified':False,
        }
    },
    'outputs':{
        'output_0':{
            'index':0,
            'dtype':'int',
            'check_dtype': True,
            'check_col_dtypes': True, # Ignored if dtype is not df
            'check_col_order': True, # Ignored if dtype is not df
            'check_row_order': True, # Ignored if dtype is not df
            'check_column_type': True, # Ignored if dtype is not df
            'float_tolerance': 10 ** (-6)
        }
    }
}
tester = Tester(conf, key=b'kvX8VF1YUgURm8NySX8Cx_UW0ZHCC6WCFqotGOpgvQc=', path='resource/asnlib/publicdata/')
for _ in range(70):
    try:
        tester.run_test()
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
    except:
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
        raise

### BEGIN HIDDEN TESTS
tester = Tester(conf, key=b'RKLlutzjcNM-oSSPrBDwxq7ABnEKW0UkWDk2jr60_t4=', path='resource/asnlib/publicdata/encrypted/')
for _ in range(20):
    try:
        tester.run_test()
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
    except:
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
        raise
### END HIDDEN TESTS
print('Passed! Please submit.')

Exercise 4 - (3 Points):

Now it's time to do some computation.

If we use cards_info to get the suit groups from the integer representation of a hand we can perform simple operations on the suit groups (W, X, Y, Z) to combine them and extract key information for identification.

You may have forgotten about Python's bitwise operators, but it's worth reviewing the docs for a few minutes (They're short, but skip the part on negative numbers). Several of these operators will be useful for this exercise. (& | ^ >>).

We are interested in calculating the values for each variable in the table below.

Variable Type Value Meaning
flush bool True if any of W, X, Y, Z has a bit count of 5 True when all cards are the same suit
hand_ranks int bits are 1 in positions where the bits are 1 in W or X or Y or Z The distinct ranks in the hand
quads int bits are 1 in positions where the bits are 1 in W and X and Y and Z The ranks which appear four times in the hand
odd_ranks int bits are 1 in positions where the bits are 1 in W xor X xor Y xor Z The ranks which appear an odd number of times in the hand
trips int bits are 1 in positions where the
bits are 1 in odd_ranks and in_arbitrary_pair*
The ranks which appear three times in the hand
five_high bool True if hand_ranks is equal to poker_info['FIVE_HIGH']** True when the hand is a special case called a "five-high straight".
straight bool True if five_high is True or
hand_ranks&(hand_ranks>>1) has a bit count of 4
True when there are five sequential ranks in the hand
rank_count int The bit count of hand_ranks The number of distinct ranks in the hand

\* `in_arbitrary_pair` is provided in the starter code. The logic is explained in the blog post linked below but is not worth delving into in an exam setting. \*\* The five-high straight is an exception to the rank ordering given in `poker_info['RANKS']` because the sequence `'5 4 3 2 A'` is considered valid in poker.

Define get_reductions(hand: int) -> tuple as follows:

  • Input:
    • hand: the 52-bit integer representation of a collection of 5 poker cards
  • Behavior:
    • Partition the hand into four 13-bit integer "suit-groups" (implemented in starter code)
    • Calculate the values outlined in the table above. We have provided the first two in the starter code as an example.
  • Output:
    • The ordered tuple (flush, hand_ranks, quads, odd_ranks, trips, five_high, straight, rank_count)

Hint

  • This exercise draws heavily from the logic in this blog post. Feel free to use it as a reference.

Recall from the introductory material that the dictionary poker_info has a lot of useful constants and that the functions cards_info, pprint_cards, ranks_info and pprint_ranks can be used to get more information about the integer representations of collections of cards or ranks.

In [ ]:
### Define demo inputs

with open('resource/asnlib/publicdata/demo_hands_ex4.pkl', 'rb') as f:
    import dill as pickle
    demo_hands_ex4 = [v['hand'] for v in pickle.load(f).values()]
    
demo_hands_ex4
The demo included in the solution cell below should display the following output: ``` get_reductions(34084860461056) -> (True, 62, 0, 62, 0, False, True, 5) get_reductions(2252899459547138) -> (False, 4098, 2, 4096, 0, False, False, 2) get_reductions(3378112070942720) -> (False, 6144, 0, 4096, 4096, False, False, 2) get_reductions(595935302254592) -> (True, 1084, 0, 1084, 0, False, False, 5) get_reductions(131958575202304) -> (False, 496, 0, 496, 0, False, True, 5) get_reductions(598684148441088) -> (False, 1089, 0, 1089, 1, False, False, 3) get_reductions(1144044063293440) -> (False, 2081, 0, 2048, 0, False, False, 3) get_reductions(80815178383360) -> (False, 147, 0, 131, 0, False, False, 4) get_reductions(723616090030080) -> (False, 3364, 0, 3364, 0, False, False, 5) ```
In [ ]:
### Exercise 4 solution
def get_reductions(hand: int) -> tuple:
    ### Predefined values in starter code
    
    # W, X, Y, and Z are the four suit groups from the hand
    W, X, Y, Z = [val for val in cards_info(hand)['suit_groups'].values()]
    in_arbitrary_pair = (W & X) | (Y & Z)
    
    # We are also providing flush and hand_ranks as examples
    flush =         (count_bits(W)==5) | (count_bits(X)==5) | (count_bits(Y)==5) | (count_bits(Z)==5)
    hand_ranks =    W | X | Y | Z  
    ### BEGIN SOLUTION
    odd_ranks =     W ^ X ^ Y ^ Z
    trips =         odd_ranks & in_arbitrary_pair
    quads =         W & X & Y & Z
    five_high =     hand_ranks == poker_info["FIVE_HIGH"]
    straight =      five_high | (count_bits(hand_ranks&(hand_ranks>>1)) == 4)
    rank_count =    count_bits(hand_ranks)
    return flush, hand_ranks, quads, odd_ranks, trips, five_high, straight, rank_count
    ### END SOLUTION
    
### demo function call
for hand in demo_hands_ex4:
    print(f'get_reductions({hand}) -> {get_reductions(hand)}')

The cell below will test your solution for Exercise 4. The testing variables will be available for debugging under the following names in a dictionary format.

  • input_vars - Input variables for your solution.
  • original_input_vars - Copy of input variables from prior to running your solution. These should be the same as input_vars - otherwise the inputs were modified by your solution.
  • returned_output_vars - Outputs returned by your solution.
  • true_output_vars - The expected output. This should "match" returned_output_vars based on the question requirements - otherwise, your solution is not returning the correct output.
In [ ]:
### test_cell_ex4
### BEGIN HIDDEN TESTS
import dill
import hashlib
with open('resource/asnlib/public/hash_check.pkl', 'rb') as f:
    hash_check = dill.load(f)
for fname in ['testers.py', '__init__.py', 'test_utils.py']:
    hash_check(f'tester_fw/{fname}', f'resource/asnlib/public/{fname}')
del hash_check
del dill
del hashlib
### END HIDDEN TESTS
from tester_fw.testers import Tester

conf = {
    'case_file':'tc_4', 
    'func': get_reductions, # replace this with the function defined above
    'inputs':{ # input config dict. keys are parameter names
        'hand':{
            'dtype':'int', # data type of param.
            'check_modified':False,
        }
    },
    'outputs':{
        'flush':{
            'index':0,
            'dtype':'bool',
            'check_dtype': True,
            'check_col_dtypes': True, # Ignored if dtype is not df
            'check_col_order': True, # Ignored if dtype is not df
            'check_row_order': True, # Ignored if dtype is not df
            'check_column_type': True, # Ignored if dtype is not df
            'float_tolerance': 10 ** (-6)
        },
        'hand_ranks':{
            'index':1,
            'dtype':'int',
            'check_dtype': True,
            'check_col_dtypes': True, # Ignored if dtype is not df
            'check_col_order': True, # Ignored if dtype is not df
            'check_row_order': True, # Ignored if dtype is not df
            'check_column_type': True, # Ignored if dtype is not df
            'float_tolerance': 10 ** (-6)
        },
        'quads':{
            'index':2,
            'dtype':'int',
            'check_dtype': True,
            'check_col_dtypes': True, # Ignored if dtype is not df
            'check_col_order': True, # Ignored if dtype is not df
            'check_row_order': True, # Ignored if dtype is not df
            'check_column_type': True, # Ignored if dtype is not df
            'float_tolerance': 10 ** (-6)
        },
        'odd_ranks':{
            'index':3,
            'dtype':'int',
            'check_dtype': True,
            'check_col_dtypes': True, # Ignored if dtype is not df
            'check_col_order': True, # Ignored if dtype is not df
            'check_row_order': True, # Ignored if dtype is not df
            'check_column_type': True, # Ignored if dtype is not df
            'float_tolerance': 10 ** (-6)
        },
        'trips':{
            'index':4,
            'dtype':'int',
            'check_dtype': True,
            'check_col_dtypes': True, # Ignored if dtype is not df
            'check_col_order': True, # Ignored if dtype is not df
            'check_row_order': True, # Ignored if dtype is not df
            'check_column_type': True, # Ignored if dtype is not df
            'float_tolerance': 10 ** (-6)
        },
        'five_high':{
            'index':5,
            'dtype':'bool',
            'check_dtype': True,
            'check_col_dtypes': True, # Ignored if dtype is not df
            'check_col_order': True, # Ignored if dtype is not df
            'check_row_order': True, # Ignored if dtype is not df
            'check_column_type': True, # Ignored if dtype is not df
            'float_tolerance': 10 ** (-6)
        },
        'straight':{
            'index':6,
            'dtype':'bool',
            'check_dtype': True,
            'check_col_dtypes': True, # Ignored if dtype is not df
            'check_col_order': True, # Ignored if dtype is not df
            'check_row_order': True, # Ignored if dtype is not df
            'check_column_type': True, # Ignored if dtype is not df
            'float_tolerance': 10 ** (-6)
        },
        'rank_count':{
            'index':7,
            'dtype':'int',
            'check_dtype': True,
            'check_col_dtypes': True, # Ignored if dtype is not df
            'check_col_order': True, # Ignored if dtype is not df
            'check_row_order': True, # Ignored if dtype is not df
            'check_column_type': True, # Ignored if dtype is not df
            'float_tolerance': 10 ** (-6)
        }
    }
}
tester = Tester(conf, key=b'kvX8VF1YUgURm8NySX8Cx_UW0ZHCC6WCFqotGOpgvQc=', path='resource/asnlib/publicdata/')
for _ in range(500):
    try:
        tester.run_test()
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
    except:
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
        raise

### BEGIN HIDDEN TESTS
tester = Tester(conf, key=b'RKLlutzjcNM-oSSPrBDwxq7ABnEKW0UkWDk2jr60_t4=', path='resource/asnlib/publicdata/encrypted/')
for _ in range(20):
    try:
        tester.run_test()
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
    except:
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
        raise
### END HIDDEN TESTS
print('Passed! Please submit.')

Exercise 5 - (3 Points):

With the 8 reductions from the hand we can now calculate its strength attributes. In the chart below see that each rank_count value and branch condition identifies a hand type and how to determine the primary and secondary ranks for that hand type.

rank_count branch condition type primary ranks secondary ranks
2 Any rank is in 4 suits FOUR_OF_A_KIND Rank in 4 suits Rank in the hand but not in 4 suits
2 No rank is in 4 suits FULL_HOUSE Ranks in an odd number of suits Rank in the hand but not in an odd number of suits
3 Any rank is in 3 suits THREE_OF_A_KIND Rank in 3 suits Ranks in the hand but not in 3 suits
3 No rank is in 3 suits TWO_PAIR Ranks in the hand but not in an odd number of suits Ranks in an odd number of suits
4 True PAIR Ranks in the hand but not in an odd number of suits Ranks in an odd number of suits
5 All cards the same suit and all ranks sequential STRAIGHT_FLUSH Ranks in the hand (see note) 0
5 All cards the same suit but ranks not sequential FLUSH Ranks in the hand 0
5 All ranks sequential but not all cards are the same suit STRAIGHT Ranks in the hand (see note) 0
5 Ranks not sequential not all cards are the same suit HIGH_CARD Ranks in the hand 0

Note In the special case of a five-high straight or five-high straight-flush, 15 should be returned as the primary ranks.

Your task
Define identify_from_reductions(flush: bool, hand_ranks: int, quads: int, odd_ranks: int, trips: int, five_high: bool, straight: bool, rank_count: int) -> tuple

  • Inputs:
    • flush: True if all cards are the same suit. False otherwise
    • hand_ranks: 13-bit integer representation of all the ranks in the hand
    • quads: 13-bit integer representation of all the ranks appearing in 4 suits
    • odd_ranks: 13-bit integer representation of all ranks appearing in an odd number of suits
    • trips: 13-bit integer representation of all the ranks in 3 suits
    • five_high: True if the ranks in the hand meet the criteria for a five high straight False otherwise
    • straight: True if all ranks in the hand are sequential. False otherwise
    • rank_count: count of the number of distinct ranks in the hand
  • Behavior:
    • Evaluate the hand type, primary ranks, and secondary ranks of the hand per the logic in the table above
  • Output:
    • Ordered tuple (str, int, int) containing the hand type, primary ranks, and secondary ranks

Recall from the introductory material that the dictionary poker_info has a lot of useful constants and that the functions cards_info, pprint_cards, ranks_info and pprint_ranks can be used to get more information about the integer representations of collections of cards or ranks.

In [ ]:
# demo_reductions_ex5 = [random_type_example(t)['reductions'] for t in poker_info['HAND_TYPES']]
with open('resource/asnlib/publicdata/demo_reductions.pkl', 'rb') as f:
    demo_reductions_ex5 = pickle.load(f)

The demo included in the solution cell below should display the following output:

('STRAIGHT_FLUSH', 124, 0) 

('FOUR_OF_A_KIND', 128, 64) 

('FULL_HOUSE', 2048, 1024) 

('FLUSH', 3400, 0) 

('STRAIGHT', 62, 0) 

('THREE_OF_A_KIND', 512, 1032) 

('TWO_PAIR', 2064, 4) 

('PAIR', 16, 578) 

('HIGH_CARD', 454, 0)

Note: The demo iterates over a number of inputs and shows both inputs and outputs. You only need to return the outputs.

In [ ]:
### Exercise 5 solution
def identify_from_reductions(flush: bool,
                          hand_ranks: int,
                          quads: int,
                          odd_ranks: int,
                          trips: int,
                          five_high: bool,
                          straight: bool,
                          rank_count: int) -> tuple:
    ### BEGIN SOLUTION
    if rank_count == 2:
        if quads:
            return 'FOUR_OF_A_KIND', quads, odd_ranks
        else:
            return 'FULL_HOUSE', odd_ranks, hand_ranks ^ odd_ranks
    if rank_count == 3:
        if hand_ranks == odd_ranks:
            return 'THREE_OF_A_KIND', trips, hand_ranks ^ trips
        else:
            return 'TWO_PAIR', hand_ranks ^ odd_ranks, odd_ranks
    if rank_count == 4:
        return 'PAIR', hand_ranks^odd_ranks, odd_ranks
    if rank_count == 5:
        primary = 15 if five_high else hand_ranks
        if straight and flush:
            hand_type = 'STRAIGHT_FLUSH'
        elif flush:
            hand_type = 'FLUSH'
        elif straight:
            hand_type = 'STRAIGHT'
        else:
            hand_type = 'HIGH_CARD'
        return hand_type, primary, 0
    ### END SOLUTION
    
for reduction in demo_reductions_ex5:
    print(reduction, '---------->              ')
    print(identify_from_reductions(**reduction), '\n')

The cell below will test your solution for Exercise 5. The testing variables will be available for debugging under the following names in a dictionary format.

  • input_vars - Input variables for your solution.
  • original_input_vars - Copy of input variables from prior to running your solution. These should be the same as input_vars - otherwise the inputs were modified by your solution.
  • returned_output_vars - Outputs returned by your solution.
  • true_output_vars - The expected output. This should "match" returned_output_vars based on the question requirements - otherwise, your solution is not returning the correct output.
In [ ]:
### test_cell_ex5
### BEGIN HIDDEN TESTS
import dill
import hashlib
with open('resource/asnlib/public/hash_check.pkl', 'rb') as f:
    hash_check = dill.load(f)
for fname in ['testers.py', '__init__.py', 'test_utils.py']:
    hash_check(f'tester_fw/{fname}', f'resource/asnlib/public/{fname}')
del hash_check
del dill
del hashlib
### END HIDDEN TESTS
from tester_fw.testers import Tester

conf = {
    'case_file':'tc_5', 
    'func': identify_from_reductions, # replace this with the function defined above
    'inputs':{
        'rank_count': {'dtype': 'int', 'check_modified': False},
         'hand_ranks': {'dtype': 'int', 'check_modified': False},
         'odd_ranks': {'dtype': 'int', 'check_modified': False},
         'quads': {'dtype': 'int', 'check_modified': False},
         'flush': {'dtype': 'bool', 'check_modified': False},
         'trips': {'dtype': 'int', 'check_modified': False},
         'straight': {'dtype': 'bool', 'check_modified': False},
         'five_high': {'dtype': 'bool', 'check_modified': False}
    },
    'outputs':{
            'hand_type': {'index': 0,
              'dtype': 'str',
              'check_dtype': True,
              'check_col_dtypes': True,
              'check_col_order': True,
              'check_row_order': True,
              'check_column_type': True,
              'float_tolerance': 1e-06},
             'primary': {'index': 1,
              'dtype': 'int',
              'check_dtype': True,
              'check_col_dtypes': True,
              'check_col_order': True,
              'check_row_order': True,
              'check_column_type': True,
              'float_tolerance': 1e-06},
             'secondary': {'index': 2,
              'dtype': 'int',
              'check_dtype': True,
              'check_col_dtypes': True,
              'check_col_order': True,
              'check_row_order': True,
              'check_column_type': True,
              'float_tolerance': 1e-06}
    }
}
tester = Tester(conf, key=b'kvX8VF1YUgURm8NySX8Cx_UW0ZHCC6WCFqotGOpgvQc=', path='resource/asnlib/publicdata/')
for _ in range(70):
    try:
        tester.run_test()
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
    except:
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
        raise

### BEGIN HIDDEN TESTS
tester = Tester(conf, key=b'RKLlutzjcNM-oSSPrBDwxq7ABnEKW0UkWDk2jr60_t4=', path='resource/asnlib/publicdata/encrypted/')
for _ in range(20):
    try:
        tester.run_test()
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
    except:
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
        raise
### END HIDDEN TESTS
print('Passed! Please submit.')

Exercise 6 - (2 Points):

Now that we've identified the strength attributes of the hand we need some way to compare them. We can leverage integers again. If we use the lookup dictionary in poker_info we can translate the string representation of each hand type into its relative strength. The stronger types are higher. Lets construct an integer as follows:

  • Bit positions 26-29 are the relative strength of the hand type (can be extracted from poker_info)
  • Bit positions 13-25 are the primary ranks of the hand
  • Bit positions 0-12 are the secondary ranks of the hand

For example ('THREE_OF_A_KIND', 512, 1032):

TYPE:      0100
PRIMARY:   0001000000000
SECONDARY: 0010000001000

TYPE | PRIMARY       | SECONDARY
---- | -------       | ---------
0100 | 0001000000000 | 0010000001000

Score in binary                  | base-10
---------------                  | -------
0b010000010000000000010000001000 | 272630792

The result will always be larger for stronger poker hands.

Define score_from_info(hand_type: str, primary: int, secondary: int) -> int as follows:

  • Inputs:
    • hand_type - string representing a type of hand. Will be a key of poker_info['HAND_TYPES']
    • primary - 13-bit integer representation of the primary ranks of a hand
    • secondary - 13-bit integer representation of the secondary ranks of a hand
  • Behavior:
    • Determine the relative strength of the hand type. There is a mapping from name to relative strength in poker_info.
    • Concatenate the bits for relative type strength, primary and secondary ranks to form the score
    • HINT - In the next exercise starter code we provide a utility for unpacking an integer of this form into its components.
  • Output:
    • 30-bit integer score

Recall from the introductory material that the dictionary poker_info has a lot of useful constants and that the functions cards_info, pprint_cards, ranks_info and pprint_ranks can be used to get more information about the integer representations of collections of cards or ranks.

The demo included in the solution cell below should display the following output: ``` 272630792 ```
In [ ]:
### Exercise 6 solution
def score_from_info(hand_type: str, primary: int, secondary: int) -> int:
    ### BEGIN SOLUTION
    return (poker_info['HAND_TYPES'][hand_type] << 26) + (primary << 13) + secondary  
    ### END SOLUTION
    
### demo function call
score_from_info('THREE_OF_A_KIND', 512, 1032)

The cell below will test your solution for Exercise 6. The testing variables will be available for debugging under the following names in a dictionary format.

  • input_vars - Input variables for your solution.
  • original_input_vars - Copy of input variables from prior to running your solution. These should be the same as input_vars - otherwise the inputs were modified by your solution.
  • returned_output_vars - Outputs returned by your solution.
  • true_output_vars - The expected output. This should "match" returned_output_vars based on the question requirements - otherwise, your solution is not returning the correct output.
In [ ]:
### test_cell_ex6
### BEGIN HIDDEN TESTS
import dill
import hashlib
with open('resource/asnlib/public/hash_check.pkl', 'rb') as f:
    hash_check = dill.load(f)
for fname in ['testers.py', '__init__.py', 'test_utils.py']:
    hash_check(f'tester_fw/{fname}', f'resource/asnlib/public/{fname}')
del hash_check
del dill
del hashlib
### END HIDDEN TESTS
from tester_fw.testers import Tester

conf = {
    'case_file':'tc_6', 
    'func': score_from_info, # replace this with the function defined above
    'inputs':{
        'hand_type': {'dtype': 'str', 'check_modified': False},
     'primary': {'dtype': 'int', 'check_modified': False},
     'secondary': {'dtype': 'int', 'check_modified': False}
    },
    'outputs':{
        'output_0':{
            'index':0,
            'dtype':'int',
            'check_dtype': True,
            'check_col_dtypes': True, # Ignored if dtype is not df
            'check_col_order': True, # Ignored if dtype is not df
            'check_row_order': True, # Ignored if dtype is not df
            'check_column_type': True, # Ignored if dtype is not df
            'float_tolerance': 10 ** (-6)
        }
    }
}
tester = Tester(conf, key=b'kvX8VF1YUgURm8NySX8Cx_UW0ZHCC6WCFqotGOpgvQc=', path='resource/asnlib/publicdata/')
for _ in range(70):
    try:
        tester.run_test()
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
    except:
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
        raise

### BEGIN HIDDEN TESTS
tester = Tester(conf, key=b'RKLlutzjcNM-oSSPrBDwxq7ABnEKW0UkWDk2jr60_t4=', path='resource/asnlib/publicdata/encrypted/')
for _ in range(20):
    try:
        tester.run_test()
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
    except:
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
        raise
### END HIDDEN TESTS
print('Passed! Please submit.')

Exercise 7 - (2 Points):

Translate the score for a poker hand into human readable versions of its components

  • Inputs:
    • score: the score for a poker hand
  • Behavior:
    • Partition the score into its type_score, primary_ranks, and secondary_ranks components
    • Translate the type_score into the corresponding hand type
      • Recall poker_info['HAND_TYPES'] maps hand types to type_score values.
    • Translate both "_ranks" components into a Python set containing the human readable ranks
      • 0b1100000000000 = 1536 -> {'A', 'K'}
  • Output:
    • Dictionary with these key/value pairs:
      • hand_type: (str) - the hand type
      • primary: (set) - set of the primary ranks
      • secondary: (set) - set of the secondary ranks

Note

  • We have provided partition_score, a function that partitions the score into its components.
  • If the partition for the secondary component of the score is 0 then an empty set should be returned.

Recall from the introductory material that the dictionary poker_info has a lot of useful constants and that the functions cards_info, pprint_cards, ranks_info and pprint_ranks can be used to get more information about the integer representations of collections of cards or ranks.

The demo included in the solution cell below should display the following output: ``` {'hand_type': 'THREE_OF_A_KIND', 'primary': {'T'}, 'secondary': {'4', 'A'}} ```
In [ ]:
### Exercise 7 solution
def partition_score(score: int) -> tuple:
    '''Partitions a poker hand score into its type strength, primary ranks, and secondary ranks components
    '''
    FOUR_BITS = 0b1111
    THIRTEEN_BITS = 0b1111111111111
    
    type_score = FOUR_BITS & score>>26         # shift right by 26 and keep first 4 bits
    primary_ranks = THIRTEEN_BITS & score>>13  # shift right by 13 and keep first 13 bits
    secondary_ranks = THIRTEEN_BITS & score    # keep first 13 bits
    return type_score, primary_ranks, secondary_ranks

def translate_score(score: int) -> dict:
    type_score, primary_ranks, secondary_ranks = partition_score(score)
    ### BEGIN SOLUTION
    REVERSE_HAND_TYPES = reverse_dict(poker_info['HAND_TYPES'])
    return {
        'hand_type': REVERSE_HAND_TYPES[type_score],
        'primary': set(ranks_info(primary_ranks)['string_representation'].split()),
        'secondary': set(ranks_info(secondary_ranks)['string_representation'].split())
    }
    ### END SOLUTION
    
### demo function call

translate_score(270536708)

The cell below will test your solution for Exercise 7. The testing variables will be available for debugging under the following names in a dictionary format.

  • input_vars - Input variables for your solution.
  • original_input_vars - Copy of input variables from prior to running your solution. These should be the same as input_vars - otherwise the inputs were modified by your solution.
  • returned_output_vars - Outputs returned by your solution.
  • true_output_vars - The expected output. This should "match" returned_output_vars based on the question requirements - otherwise, your solution is not returning the correct output.
In [ ]:
### test_cell_ex7
### BEGIN HIDDEN TESTS
import dill
import hashlib
with open('resource/asnlib/public/hash_check.pkl', 'rb') as f:
    hash_check = dill.load(f)
for fname in ['testers.py', '__init__.py', 'test_utils.py']:
    hash_check(f'tester_fw/{fname}', f'resource/asnlib/public/{fname}')
del hash_check
del dill
del hashlib
### END HIDDEN TESTS
from tester_fw.testers import Tester

conf = {
    'case_file':'tc_7', 
    'func': translate_score, # replace this with the function defined above
    'inputs':{ # input config dict. keys are parameter names
        'score':{
            'dtype':'int', # data type of param.
            'check_modified':True,
        }
    },
    'outputs':{
        'output_0':{
            'index':0,
            'dtype':'dict',
            'check_dtype': True,
            'check_col_dtypes': True, # Ignored if dtype is not df
            'check_col_order': True, # Ignored if dtype is not df
            'check_row_order': True, # Ignored if dtype is not df
            'check_column_type': True, # Ignored if dtype is not df
            'float_tolerance': 10 ** (-6)
        }
    }
}
tester = Tester(conf, key=b'kvX8VF1YUgURm8NySX8Cx_UW0ZHCC6WCFqotGOpgvQc=', path='resource/asnlib/publicdata/')
for _ in range(70):
    try:
        tester.run_test()
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
    except:
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
        raise

### BEGIN HIDDEN TESTS
tester = Tester(conf, key=b'RKLlutzjcNM-oSSPrBDwxq7ABnEKW0UkWDk2jr60_t4=', path='resource/asnlib/publicdata/encrypted/')
for _ in range(20):
    try:
        tester.run_test()
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
    except:
        (input_vars, original_input_vars, returned_output_vars, true_output_vars) = tester.get_test_vars()
        raise
### END HIDDEN TESTS
print('Passed! Please submit.')

Fin. If you have made it this far, congratulations on completing the exam. Don't forget to submit!