# Advent of Code 2023 — Day 4

My favourite so far!

Inspired by some folks at work, I decided to try and do this as functionally as possible.

Apparently there are some similarities with past puzzles, but I, for the life of me, cannot remember any of the puzzles of years past 😆.

## Part 1

Today's puzzle revolved around scratch-and-win cards.

A card looks like this:

```
Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
```

On the left of the pipe are your numbers, on the right, are numbers that have been chosen as winners. Your first winning number was worth 1 point, and each subsequent win would *double* your score.

So 4 wins would be `(1 * 2 * 2 * 2) = 8`

In part 1, we need to calculate the score of our card.

```
input
.map { |l|
l.gsub(/Card \d+[:]\s/, "")
.split("|")
.map{|v| v.split(" ")}
}
.map { |c|
c[0] & c[1]
}
.map { |c|
next 0 if c.count <= 0
1 * 2**(c.count-1)
}
.inject(:+)
```

That was my code for part 1.

First throw away the `Card N:`

bits, then split my numbers from the winning numbers, and finally split both sides in to their own array.

Next, map over each pair and count the matches (`n`

), then for each card, get the score `1 * 2^(n-1)`

and finally sum everything up!

⭐️ Success!

## Part 2

Enter, recursion~

In this part, instead of winning points for a card, we win *more cards!*, which in turn *win MORE cards* — and so on until we're out of cards.

The ask this time? How many cards did we win in total.

First up a couple of functions to make this a bit nicer:

```
def play(cards, index)
@tally += 1
cards
.slice(index + 1, wins(cards[index]))
.each_with_index do |_, idx|
play(cards, index + 1 + idx)
end
end
def wins(card)
(card[0] & card[1]).count
end
```

`play`

loops over all the cards we've won and plays each one, which then loops over all the cards they win and plays each one, which then loops ...

`wins`

was just a convenience function for calculating wins.

Now we can do our tallying:

```
@tally = 0
cards = input
.map { |l|
l.gsub(/Card \d+[:]\s/, "")
.split("|")
.map{|v| v.split(" ")}
}
cards.each_with_index do |_, index|
play(cards, index)
end
@tally
```

First throw away all of the `Cards:`

info and pre-process our cards in to the same shape as before, then, play each round.

After many `SystemStackError: stack level too deep`

errors, I got there (always remember to increment your indices folks).

⭐️ Success!

## Performance

This one was a good candidate for some perf gains.

```
Rehearsal ------------------------------------------
part 1
0.001061 0.000019 0.001080 ( 0.001079)
part 2
13.300407 0.015743 13.316150 ( 13.330276)
-------------------------------- total: 13.317230sec
user system total real
part 1
0.000637 0.000006 0.000643 ( 0.000644)
part 2
13.554139 0.020912 13.575051 ( 13.608991)
```

The code above for part 2 was suuuuper slow. Clocking in at ~13s.

```
Rehearsal ------------------------------------------
part 1
0.001129 0.000038 0.001167 ( 0.001173)
part 2
2.834447 0.007401 2.841848 ( 2.845803)
--------------------------------- total: 2.843015sec
user system total real
part 1
0.000636 0.000003 0.000639 ( 0.000637)
part 2
2.838135 0.007346 2.845481 ( 2.849414)
```

After adding a super simple cache to `wins`

, it shaved ~10s off the total time:

```
def wins(card_id, card)
@win_cache[card_id] ||= (card[0] & card[1]).count
end
```

Happy with that!

Day 4: https://github.com/dNitza/advent-of-code/tree/main/2023/04

in posts

Tagged