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.
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
Examples
$ ./ch-1.py 2 3 -1 8 4
3
$ ./ch-1.py 1 -1 4
2
$ ./ch-1.py 2 5
-1
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:
- How many different 5-card hands can be dealt?
- 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
- 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)
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)
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'
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'
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'
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'
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'
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))
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
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)