DEV Community

Simon Green
Simon Green

Posted on

Index and poker games

Weekly Challenge 291

Each week Mohammad S. Anwar sends out The Weekly Challenge, a chance for all of us to come up with solutions to two weekly tasks. My solutions are written in Python first, and then converted to Perl. It's a great way for us all to practice some coding.

Challenge, My solutions

Task 1: Middle Index

Tasks

You are given an array of integers, @ints.

Write a script to find the leftmost middle index (MI) i.e. the smallest amongst all the possible ones.

A middle index is an index where ints[0] + ints[1] + … + ints[MI-1] == ints[MI+1] + ints[MI+2] + … + ints[ints.length-1].

  • If MI == 0, the left side sum is considered to be 0. Similarly,
  • if MI == ints.length - 1, the right side sum is considered to be 0.

My solution

This is relatively straight forward. I loop through the position from 0 to one less than the length of the inputs. At each position I see if the condition is met.

def middle_index(ints: list) -> int:
    for i in range(len(ints)):
        if sum(ints[:i]) == sum(ints[i + 1:]):
            # It is, so return this position
            return i

    return -1
Enter fullscreen mode Exit fullscreen mode

Examples

$ ./ch-1.py 2 3 -1 8 4
3

$ ./ch-1.py 1 -1 4
2

$ ./ch-1.py 2 5
-1
Enter fullscreen mode Exit fullscreen mode

Task 2: Poker Hand Rankings

Task

A draw poker hand consists of 5 cards, drawn from a pack of 52: no jokers, no wild cards. An ace can rank either high or low.

Write a script to determine the following three things:

  1. How many different 5-card hands can be dealt?
  2. How many different hands of each of the 10 ranks can be dealt? See here for descriptions of the 10 ranks of Poker hands: https://en.wikipedia.org/wiki/List_of_poker_hands#Hand-ranking_categories
  3. Check the ten numbers you get in step 2 by adding them together and showing that they're equal to the number you get in step 1.

My solution

Strap in, because this is going to be a long post. It's also the first time in a long time that a task hasn't required any input. In the challenges I've completed, the last one was #177.

To answer the first question, there are 311,875,200 possible permutations of cards that can be dealt (52 × 51 × 50 × 49 × 48). However, the order of cards does not matter. For any five drawn cards, they can be arranged in 120 ways (5 × 4 × 3 × 2 × 1). Therefore there are 2,598,960 unique combinations.

I start by creating a deck of cards. For this I have a rank (card number) of 1 to 13. 1 is an ace, 2 to 10 are the numbers, 11 is Jack, 12 is Queen and the King is 13. I also have a suit s, c, d and h (spare, club, diamond and heart respectively). Using a double for loop, I generate all 52 cards (a tuple of rank and suit) and store this in a list called deck.

I then loop through each unique five card combination of deck, and determine what hand I hold. Finally I print the results.

from collections import Counter, defaultdict
from itertools import combinations

def main():
    deck = [(rank, suit) for rank in range(1, 14) for suit in ('s', 'c', 'd', 'h')]
    hands = defaultdict(int)

    for cards in combinations(deck, 5):
        hand = get_hand(cards)
        hands[hand] += 1

    display_results(hands)
Enter fullscreen mode Exit fullscreen mode

That's the easy part :)

For the get_hands function, I start by creating a dict of lists sorting by rank (the number on the card) and suit (the symbol on the card). I also count the frequency of ranks, as this is often used to determine the hand.

def get_hand(cards):
    cards_by_rank = defaultdict(list)
    cards_by_suit = defaultdict(list)

    for card in cards:
        number, suit = card
        cards_by_rank[number].append(card[1])
        cards_by_suit[suit].append(card[0])

    count_by_rank = Counter(len(cards_by_rank[r]) for r in cards_by_rank)
Enter fullscreen mode Exit fullscreen mode

So for the cards 10s, 10h, 9d, 8h, 2d, the following would be set:

  • cards_by_rank {10: ['s', 'h'], 9: ['d'], 8: ['h'], 2: ['d']}
  • cards_by_suit {'s': [10], 'h': [10, 8], 'd': [9, 2]}
  • count_by_rank {1: 3, 2: 1} (there are three ranks that appear once, and one that has two cards)

It's then time to determine what hand I am holding. We'll start with the straight flush and flush. These are the only hands that consider the suit of the cards, and that all fives cards are of the same suit. This is determined when the cards_by_suit dict only has one value.

To determine if it is a straight flush, I order the cards numerically (from 1 to 13). If the first card is 1 (an ace) and the last card is 13 (king), I remove the first card and append 14 to the end of the list. This allows a 10, Jack, Queen, King and Ace to be considered a straight flush. A straight flush occurs when the difference between the first card number and last card is four.

    if len(cards_by_suit) == 1:
        cards = sorted(cards_by_rank)
        if cards[0] == 1 and cards[4] == 13:
            cards.pop(0)
            cards.append(14)

        if cards[4] - cards[0] == 4:
            return 'Straight flush'

        return 'Flush'
Enter fullscreen mode Exit fullscreen mode

For the four of a kind hand (four of one rank, and a random last card) and full house (three of one rank, two of a different rank), I can use the count_by_rank dict to see if the hand matches the specified criteria.

    if count_by_rank[4]:
        return 'Four of a kind'

    if count_by_rank[3] and count_by_rank[2]:
        return 'Full house'
Enter fullscreen mode Exit fullscreen mode

To determine if the hand is straight, I use a similar logic to the straight flush. I first check that I have five unique ranks (card numbers), order them, move the ace if required, and check if the difference between high and low is 4.

    if len(cards_by_rank) == 5:
        # Get the card ranks in the possible flush
        cards = sorted(cards_by_rank)
        if cards[0] == 1 and cards[4] == 13:
            cards.pop(0)
            cards.append(14)

        if cards[4] - cards[0] == 4:
            return 'Straight'
Enter fullscreen mode Exit fullscreen mode

Three of a kind (three cards of a single rank, two cards of different ranks), two pairs (two cards of one rank, two cards of a different rank, random last card), one pair (two cards of one rank, three cards of a different rank each) can all be calculated using the count_by_rank dict.

    if count_by_rank[3]:
        return 'Three of a kind'

    if count_by_rank[2] == 2:
        return 'Two pair'

    if count_by_rank[2]:
        return 'One pair'
Enter fullscreen mode Exit fullscreen mode

And finally if nothing matches, return 'High card'. You definitely won't want to bet your house if you are holding this hand :)

    return 'High card'
Enter fullscreen mode Exit fullscreen mode

The display_results function simply displays the results (ordered by rank) in a uniformed layout. As mentioned at the start for each combination there are 120 permutations the card could be ordered in.

def display_results(hands):
    hand_names = [
        'Straight flush', 'Four of a kind', 'Full house', 'Flush', 'Straight',
        'Three of a kind', 'Two pair', 'One pair', 'High card',
    ]

    fmt = '{:16} {:>17,} {:>12,}'

    print('Hand                  Combinations Permutations')
    print('-------------------   ------------ ------------')

    for hand in hand_names:
        print(fmt.format(hand, hands[hand], hands[hand] * 120))

    print('-------------------   ------------ ------------')
    print(fmt.format('Total', sum(hands.values()), sum(hands.values()) * 120))
Enter fullscreen mode Exit fullscreen mode

Output

$ ./ch-2.py 
Hand                  Combinations Permutations
-------------------   ------------ ------------
Straight flush                  40        4,800
Four of a kind                 624       74,880
Full house                   3,744      449,280
Flush                        5,108      612,960
Straight                    10,200    1,224,000
Three of a kind             54,912    6,589,440
Two pair                   123,552   14,826,240
One pair                 1,098,240  131,788,800
High card                1,302,540  156,304,800
-------------------   ------------ ------------
Total                    2,598,960  311,875,200
Enter fullscreen mode Exit fullscreen mode

This took about 15 seconds to run on my home PC.

As we can see from the bottom row, we have 2,598,960 combinations and 311,875,200 permutations. This matches what we expected to see in the output.

Top comments (0)