# Advent of Code 2023 — Day 7

Day 7 saw us playing cards, on camels — camel cards!

Camel cards are like poker. But simpler, so you can play it while on a camel.

The hand-rules are similar to Poker, but with a few changes — which is ace, because I have no idea what the poker rules are anymore.

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`

The hands are also ranked by suit, in descending order where an ace is high.

## Part 1

In part 1, we have the following input:

```
32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483
```

Where the right hand column is the bid amount for the hand.

Our job is to sort each hand, and then multiply each hand's bid with its rank.

So if we take the order above as an example — we'd have `765 * 1`

+ `684 * 2`

+ `...`

I started off this puzzle by assigning a suit / card number a letter (easier to sort alphabetically than numerically in this case):

```
ORDER = {"A" => "a", "K" => "b", "Q" => "c", "J" => "d", "T" => "e", "9" => "f", "8" => "g", "7" => "h", "6" => "i", "5" => "j", "4" => "k", "3" => "l", "2" => "m"}.freeze
```

Then turned the input in to an array of arrays that looked like `[[hand, bid], ...]`

, sorted the list and then went to work adding up each bid:

```
rounds = input.map { |l| l.split(" ") }
sorted_rounds = rounds
.sort {|a,b| [hand_type(a[0]), hand_value(b[0])] <=> [hand_type(b[0]), hand_value(a[0])] }
sorted_rounds
.map
.with_index(1) {|r, idx| r[1].to_i * idx }
.sum
```

To find the type of hand I was looking at, I used the below 2 methods.

First, I turned the hand in to a hash with the number of cards of each type. So for an input of `K3558`

I would have `{K => 1, 3 => 1, 5 => 2, 8 => 1}`

. Then, I take the values and sort them — in to something like `[2,1,1,1]`

.

This is then passed in to the `score_hand`

method which returns a rank based on the hand.

```
def hand_type(hand)
cards = hand.gsub(/(.)\0*/).to_a
sorted_cards = cards.tally.values.sort {|a,b| b <=> a}
score_hand(sorted_cards)
end
```

```
def score_hand(cards)
return 6 if cards == [5]
return 5 if cards == [4,1]
return 4 if cards == [3,2]
return 3 if cards == [3,1,1]
return 2 if cards == [2,2,1]
return 1 if cards == [2,1,1,1]
return 0 if cards == [1,1,1,1,1]
end
```

Lastly, we need to sort cards that have the same type based on the order of the cards in hand.

For this, sorting alphabetically was was simpler than zipping each array and comparing card values.

For this, I took a look at the `ORDER`

constant and turned a hand like `K3558`

to `bljjg`

, so when compared with another hand that has the same type, say `K3448`

(`bliig`

), the first hand wins.

```
def hand_value(hand)
hand.gsub(/(.)\0*/).map{ |v| ORDER[v] }.join("")
end
```

## Part 2

In part 2, Jacks are out, Jokers are in.

Jokers are the lowest scoring card, *but* they can be used as a wildcard to improve a hand to one of a better quality. So for example, the hand `33JJ8`

goes from being two of a kind `[33, JJ]`

to four of a kind `[3333]`

!

Because Jokers are now worthless, I have a new order for them:

```
IMPROVED_ORDER = {"A" => "a", "K" => "b", "Q" => "c", "T" => "d", "9" => "e", "8" => "f", "7" => "g", "6" => "h", "5" => "i", "4" => "j", "3" => "k", "2" => "l", "J" => "m"}.freeze
```

The code for part 2 and part 1 is pretty much the same, except I try and improve each hand when sorting:

```
rounds = input
.map { |l| l.split(" ") }
sorted_rounds = rounds
.sort {|a,b| [improve_hand(a[0]), improved_hand_value(b[0])] <=> [improve_hand(b[0]), improved_hand_value(a[0])] }
sorted_rounds
.map
.with_index(1) {|r, idx| r[1].to_i * idx }
.sum
```

I try and improve each hand with the method below. I do the same tallying of each hand.

So `33JJ8`

becomes `{3 => 2, J => 2, 8 => 1}`

.

But then I take any `J`

cards and add them to whichever card type has the most already.

So now, the tally looks like `{3 => 4, 8 => 1}`

— which makes this a 4 of a kind.

*Thankfully* Jokers only improve hand quality, not overall score, so we didn't need to consider hands like `KQKQJ`

and `QKQKJ`

and apply the Joker to the King in order to get a better overall score.

```
def improve_hand(hand)
cards = hand.gsub(/(.)\0*/).to_a
return hand_type(hand) unless cards.any? { |c| c == "J" }
return hand_type(hand) if cards.all? { |c| c == "J" }
card_types = cards.tally
improve_by = card_types["J"]
type_to_improve = type_to_improve(card_types)
card_types[type_to_improve] += improve_by if improve_by && type_to_improve
card_types.delete("J")
sorted_cards = card_types.values.sort {|a,b| b <=> a}
score_hand(sorted_cards)
end
def type_to_improve(cards)
return nil if cards.empty?
no_jack = cards.reject{|k,v| k == "J" }
return nil if no_jack.empty?
no_jack.max_by{|_,v| v}[0]
end
```

⭐️ Success!

Day 7: https://github.com/dNitza/advent-of-code/blob/main/2023/07/lib/puzzle.rb

## Performance

```
day 07
├─ part 1 — 0.073877s
╰─ part 2 — 0.101986s
```

in posts

Tagged