This year I try to record my attempt at solving the Advent of Code 2023 riddles. This is Day 7 - see https:adventofcode.com/2023/day/7
Part 1 #
Lets first read the task:
In Camel Cards, you get a list of hands, and your goal is to order them based on the strength of each hand. A hand consists of five cards labeled one of
A
,K
,Q
,J
,T
,9
,8
,7
,6
,5
,4
,3
, or2
. The relative strength of each card follows this order, whereA
is the highest and2
is the lowest.Every hand is exactly one type. From strongest to weakest, they are:
- Five of a kind, where all five cards have the same label:
AAAAA
- Four of a kind, where four cards have the same label and one card has a different label:
AA8AA
- Full house, where three cards have the same label, and the remaining two cards share a different label:
23332
- Three of a kind, where three cards have the same label, and the remaining two cards are each different from any other card in the hand:
TTT98
- Two pair, where two cards share one label, two other cards share a second label, and the remaining card has a third label:
23432
- One pair, where two cards share one label, and the other three cards have a different label from the pair and each other:
A23A4
- High card, where all cards’ labels are distinct:
23456
Hands are primarily ordered based on type; for example, every full house is stronger than any three of a kind.
If two hands have the same type, a second ordering rule takes effect. Start by comparing the first card in each hand. If these cards are different, the hand with the stronger first card is considered stronger. If the first card in each hand have the same label, however, then move on to considering the second card in each hand. If they differ, the hand with the higher second card wins; otherwise, continue with the third card in each hand, then the fourth, then the fifth.
So,
33332
and2AAAA
are both four of a kind hands, but33332
is stronger because its first card is stronger. Similarly,77888
and77788
are both a full house, but77888
is stronger because its third card is stronger (and both hands have the same first and second card).To play Camel Cards, you are given a list of hands and their corresponding bid (your puzzle input). For example:
32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483
This example shows five hands; each hand is followed by its bid amount. Each hand wins an amount equal to its bid multiplied by its rank, where the weakest hand gets rank 1, the second-weakest hand gets rank 2, and so on up to the strongest hand. Because there are five hands in this example, the strongest hand will have rank 5 and its bid will be multiplied by 5.
So, the first step is to put the hands in order of strength:
32T3K
is the only one pair and the other hands are all a stronger type, so it gets rank 1.KK677
andKTJJT
are both two pair. Their first cards both have the same label, but the second card ofKK677
is stronger (K
vsT
), soKTJJT
gets rank 2 andKK677
gets rank 3.T55J5
andQQQJA
are both three of a kind.QQQJA
has a stronger first card, so it gets rank 5 andT55J5
gets rank 4.Now, you can determine the total winnings of this set of hands by adding up the result of multiplying each hand’s bid with its rank (
765
* 1 +220
* 2 +28
* 3 +684
* 4 +483
* 5). So the total winnings in this example are6440
.Find the rank of every hand in your set. What are the total winnings?
My input file: 2023-12-07-1-aoc.txt
As always - let’s read in the data.
import pandas as pd
camel = pd.read_table('data/2023-12-07-1-aoc.txt', delim_whitespace=True,
header=None, names=['cards', 'bid'],
dtype={'cards': 'string', 'bid': 'Int64'})
camel
cards | bid | |
---|---|---|
0 | A833A | 309 |
1 | Q33J3 | 205 |
2 | 55KK5 | 590 |
3 | K4457 | 924 |
4 | Q3QT3 | 11 |
… | … | … |
995 | KQJ53 | 367 |
996 | 57866 | 537 |
997 | Q94A9 | 210 |
998 | J448A | 903 |
999 | 6J66J | 114 |
1000 rows × 2 columns
For sorting, we need to compute a type
column. The 7 types can be deduced
based on how many different cards are there (through set()
) and the number of
cards.
Also, for the next step we prepare one column per card string in the cards
.
def get_type(cards):
cardset = set(cards)
cardlen = len(cardset)
cardlist = list(cards)
cardnum = [cardlist.count(i) for i in cardset]
if cardlen == 1:
ctype = 1
elif cardlen in (4, 5):
ctype = cardlen + 2
elif cardlen == 2:
ctype = 2 if set(cardnum) == {1, 4} else 3
else:
ctype = 4 if 3 in cardnum else 5
return ctype
camel['type'] = camel.loc[:, 'cards'].apply(lambda x: get_type(x))
for i in range(5):
camel[f'c{i}'] = camel.loc[:, 'cards'].apply(lambda x: list(x)[i])
camel
cards | bid | type | c0 | c1 | c2 | c3 | c4 | |
---|---|---|---|---|---|---|---|---|
0 | A833A | 309 | 5 | A | 8 | 3 | 3 | A |
1 | Q33J3 | 205 | 4 | Q | 3 | 3 | J | 3 |
2 | 55KK5 | 590 | 3 | 5 | 5 | K | K | 5 |
3 | K4457 | 924 | 6 | K | 4 | 4 | 5 | 7 |
4 | Q3QT3 | 11 | 5 | Q | 3 | Q | T | 3 |
… | … | … | … | … | … | … | … | … |
995 | KQJ53 | 367 | 7 | K | Q | J | 5 | 3 |
996 | 57866 | 537 | 6 | 5 | 7 | 8 | 6 | 6 |
997 | Q94A9 | 210 | 6 | Q | 9 | 4 | A | 9 |
998 | J448A | 903 | 6 | J | 4 | 4 | 8 | A |
999 | 6J66J | 114 | 3 | 6 | J | 6 | 6 | J |
1000 rows × 8 columns
For sorting, pandas
contributes the sort_values
method, which supports
sorting by multiple columns (here, we first sort according to type
, then c0
,
c1
, …). For easier sorting by numerical values, we convert the values using
the card_dict
dictionary, which implements the given sorting order.
card_dict = {c: i for i, c in enumerate([7, 6, 5, 4, 3, 2, 1, '2', '3',
'4', '5', '6', '7', '8', '9',
'T', 'J', 'Q', 'K', 'A'])}
camel = camel.sort_values(by=['type', 'c0', 'c1', 'c2', 'c3', 'c4'],
ignore_index=True,
key=lambda x: x.map(card_dict))
camel.index += 1
camel
cards | bid | type | c0 | c1 | c2 | c3 | c4 | |
---|---|---|---|---|---|---|---|---|
1 | 237AQ | 157 | 7 | 2 | 3 | 7 | A | Q |
2 | 23K47 | 759 | 7 | 2 | 3 | K | 4 | 7 |
3 | 249K8 | 612 | 7 | 2 | 4 | 9 | K | 8 |
4 | 264A7 | 341 | 7 | 2 | 6 | 4 | A | 7 |
5 | 26578 | 10 | 7 | 2 | 6 | 5 | 7 | 8 |
… | … | … | … | … | … | … | … | … |
996 | AA5AA | 565 | 2 | A | A | 5 | A | A |
997 | AAATA | 704 | 2 | A | A | A | T | A |
998 | AAAA3 | 145 | 2 | A | A | A | A | 3 |
999 | AAAAJ | 594 | 2 | A | A | A | A | J |
1000 | JJJJJ | 219 | 1 | J | J | J | J | J |
1000 rows × 8 columns
Lastly, we multiply the bid with the index and sum the result.
camel.loc[:, 'bid'].mul(camel.index).sum()
246912307
Part 2 #
Lets first read the task:
To make things a little more interesting, the Elf introduces one additional rule. Now,
J
cards are jokers - wildcards that can act like whatever card would make the hand the strongest type possible.To balance this,
J
cards are now the weakest individual cards, weaker even than2
. The other cards stay in the same order:A
,K
,Q
,T
,9
,8
,7
,6
,5
,4
,3
,2
,J
.
J
cards can pretend to be whatever card is best for the purpose of determining hand type; for example,QJJQ2
is now considered four of a kind. However, for the purpose of breaking ties between two hands of the same type,J
is always treated asJ
, not the card it’s pretending to be:JKKK2
is weaker thanQQQQ2
becauseJ
is weaker thanQ
.Now, the above example goes very differently:
32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483
32T3K
is still the only one pair; it doesn’t contain any jokers, so its strength doesn’t increase.KK677
is now the only two pair, making it the second-weakest hand.T55J5
,KTJJT
, andQQQJA
are now all four of a kind!T55J5
gets rank 3,QQQJA
gets rank 4, andKTJJT
gets rank 5.With the new joker rule, the total winnings in this example are
5905
.Using the new joker rule, find the rank of every hand in your set. What are the new total winnings?
What changes? We have to update our get_type()
function and our card_dict
dictionary, otherwise the method stays the same!
Let’s first write get_type_joker()
: if no joker is in the cards, we can just
refer to get_type()
. If we have 1, 2 or 5 different card faces, the answer is
trivial. We only have to use the number of jokers and the maximum number of
other faces if we have 3 or 4 different faces.
Note: if a J
is present, there can never be high card (type = 7
), because
we always can get one pair. Also we can never get two pair (type = 5
),
because we can always construct three of a kind or full house.
def get_type_joker(cards):
cardset = set(cards)
cardlen = len(cardset)
cardlist = list(cards)
cardnum = [cardlist.count(i) for i in cardset]
if not 'J' in cardset:
ctype = get_type(cards)
else:
idxj = list(cardset).index('J')
numj = cardnum[idxj]
cardnum.pop(idxj)
if cardlen in [1, 2]:
ctype = 1
elif cardlen == 5:
ctype = 6
else:
if (numj in [1, 2]) and (cardlen == 4):
ctype = 4
else:
ctype = 6 - max(cardnum) - numj
return ctype
camel['type'] = camel.loc[:, 'cards'].apply(lambda x: get_type_joker(x))
camel
cards | bid | type | c0 | c1 | c2 | c3 | c4 | |
---|---|---|---|---|---|---|---|---|
1 | 237AQ | 157 | 7 | 2 | 3 | 7 | A | Q |
2 | 23K47 | 759 | 7 | 2 | 3 | K | 4 | 7 |
3 | 249K8 | 612 | 7 | 2 | 4 | 9 | K | 8 |
4 | 264A7 | 341 | 7 | 2 | 6 | 4 | A | 7 |
5 | 26578 | 10 | 7 | 2 | 6 | 5 | 7 | 8 |
… | … | … | … | … | … | … | … | … |
996 | AA5AA | 565 | 2 | A | A | 5 | A | A |
997 | AAATA | 704 | 2 | A | A | A | T | A |
998 | AAAA3 | 145 | 2 | A | A | A | A | 3 |
999 | AAAAJ | 594 | 1 | A | A | A | A | J |
1000 | JJJJJ | 219 | 1 | J | J | J | J | J |
1000 rows × 8 columns
Now, we just move J
in our new card_dict_joker
to where it belongs, the rest
of the solution is the same.
card_dict_joker = {c: i for i, c in enumerate(
[7, 6, 5, 4, 3, 2, 1, 'J', '2', '3', '4', '5', '6', '7', '8', '9',
'T', 'Q', 'K', 'A'])}
camel = camel.sort_values(by=['type', 'c0', 'c1', 'c2', 'c3', 'c4'],
ignore_index=True,
key=lambda x: x.map(card_dict_joker))
camel.index += 1
display(camel)
print('Our new solution: ', camel.loc[:, 'bid'].mul(camel.index).sum())
cards | bid | type | c0 | c1 | c2 | c3 | c4 | |
---|---|---|---|---|---|---|---|---|
1 | 237AQ | 157 | 7 | 2 | 3 | 7 | A | Q |
2 | 23K47 | 759 | 7 | 2 | 3 | K | 4 | 7 |
3 | 249K8 | 612 | 7 | 2 | 4 | 9 | K | 8 |
4 | 264A7 | 341 | 7 | 2 | 6 | 4 | A | 7 |
5 | 26578 | 10 | 7 | 2 | 6 | 5 | 7 | 8 |
… | … | … | … | … | … | … | … | … |
996 | TJTTJ | 609 | 1 | T | J | T | T | J |
997 | QQJQJ | 131 | 1 | Q | Q | J | Q | J |
998 | QQQJQ | 831 | 1 | Q | Q | Q | J | Q |
999 | KKKJK | 183 | 1 | K | K | K | J | K |
1000 | AAAAJ | 594 | 1 | A | A | A | A | J |
1000 rows × 8 columns
Our new solution: 246894760