12
votes
Day 4: Scratchcards
Today's problem description: https://adventofcode.com/2023/day/4
Please post your solutions in your own top-level comment. Here's a template you can copy-paste into your comment to format it nicely, with the code collapsed by default inside an expandable section with syntax highlighting (you can replace python
with any of the "short names" listed in this page of supported languages):
<details>
<summary>Part 1</summary>
```python
Your code here.
```
</details>
Language of the Day for Day 4 is C! I think I'll try to use the harder languages first before going to simpler languages. After 2 days of functional programming I think I have had enough.
I thought parsing would be complicated, but turns out
scanf
works pretty well since the problem has fixed input size (although different between example and real inputs). Initially thewinnings
andsolve
function was the same function, but I had to refactor that to reuse it for part 2.Two tricks in my code:
/*/ ... /**/
is a good way to make commented out block of code. Just add another*
to make the first block/**/
to uncomment the entire block.1<<n
is the same as 2^nCool comment trick I hadn't seen before. I'm fond of using
#if 0
/#endif
for this and toggling between 0 and 1.I did a lot better than I expected on this one, getting top 500 on the leaderboard. Funnily enough, I originally solved part 2 with the naïve solution that processed each copy again as if it were the original card, and especially using PyPy, the computation time really wasn't a big deal (only about 5 seconds or so). If performance was supposed to be a bottleneck for today's problem, the input probably should've been longer. I wasn't happy with that solution, though, and reworked my program to actually be efficient. I expect this is more what was intended.
Solution
A fellow PyPy user! I always throw it in my shebang for AoC for the free speed boost.
It's always useful to try to force a brute force solution to work while I try to write something better, haha.
Not too bad today, and I always love an opportunity to use sets (even if they weren't strictly necessary). My Python solutions:
Part 1
Had to re-read part two's instructions to parse out exactly how copies worked, but straightforward enough once I knew what was being asked.
Part 2
Quick question.
I'm doing something pretty similar to you (grabbing the winning/mine and overlaps slightly different, but results in the same), but when I submit my answer for part 2 it's telling me my answer is too high. Would you maybe be able to point me in the correct direction?
Edit: Never mind, it was my damn REGEX, as always.
Part 2
Nice! The most annoying bugs are always in the parsing...
I haven't even read part two... but here's part one in Google Sheets. Its similar to another solution I saw in a chat. I don't see any other way around it.
Part 1
I can't find Kotlin's integer exponent operator or function. Part 2 also took a whole 1.5 seconds to do naively before I read the first comment by @lily and only tracked the counts after which it went back to instantaneous.
Kotlin
I was a bit late to the party, but now I'm all caught up. I struggled a bit with Day 1 and Day 3, where I passed the test case initially, but not the actual puzzle input. Day 4 was straightforward in comparison.
Rust
I did this in the most naive way I could think of, so it's kind of slow at 35ms for both parts. A set data structure would probably be much faster for finding the winning numbers, but it's fast enough, so I won't bother optimizing it right now.
Looks like the main difference between our solutions is the use of that set. I was able to get mine down to 24ms on the debug build, ~5ms with optimizations turned on.
Out of curiosity, how are you timing your program? Is it 5ms to solve both parts? Asking because the benchmarks for my solution in elixir are consistently around 4.5 ms or less for each part—I really would not have expected it to run faster than a rust solution!
Specifically, I'm timing only the evaluation of each solution function, which receives the puzzle input already loaded from file, as a string argument. Running on a 2019 MBP.
I haven't looked at Toric's code yet, so I don't know what we're doing differently in terms of parsing, but I'm using Rust too and with optimizations turned on, both parts together take 345 μs including the time it takes to read the file from disk for each part. Without optimizations it's more like 3.1 ms.
Ah ok, that's more what I'd expect. I was about to be pretty shocked by how much optimization the erlang VM does.
How are you parsing? Im doing parsing via nom, which is probably not the most efficient way out there.
I'm sticking to the standard library only for as long as that's tenable, so I'm just splitting on spaces etc and using
str::parse<u32>
to get numbers out. My code is linked down below.Having taken a look at your code now, I think the speed difference is probably coming from the fact that you're storing
winning_numbers
andnumbers
inBTreeSet
s and performing intersections as you go. I just calculated how many numbers on each card were winners at the time of parsing and stored that, so I never paid for node allocation or the cache misses involved in searching.just the unix
time
command, nothing fancy. it includes the startup time of the program, but not any reading a file from disk, as the file is actually pre-compiled into a string in the binary.Hm. I just ran the benchmark again to check, and it seems like I screwed up the hyperfine invocation somehow the first time around. That's what you get when you're too lazy to do an actual benchmark with criterion or divan...
This does include the startup time of the executable; I may do a proper benchmark after I solve today's problem.
heh, I was just using the unix time command, ill have to rerun with hyperfine.
EDIT:
hyperfine -N -w5
says ~3.5 msRust
Day 4
My partner and I bought and decorated a Christmas tree for the first time in our adult lives last night, so I was a little too tired to stay up past midnight and solve this one quickly. So many pine needles, everywhere!!
Elixir
Parts 1 and 2
I think the thing I had the most trouble with on this one was naming, honestly.
Part 2 seemed like it would be a challenge, but turned out to be quite straightforward once I came up with sensible—if a bit verbose—labels for the two variables that describe each card:
{self_copy_count, child_copy_count}
.It was nice that you could get away with not parsing any numbers for this one. It works fine just by comparing the numeric strings.
You don't even need to use the card IDs!
This is one of those ones where understanding the instructions was the most complicated part.
My Part 2 solution is slow, taking about 10 seconds to run on my machine, but that's good enough for me.
Part 1 - Ruby
Part 2
Feels more like a proper day 4 than the previous days did; simple and fun. I went the efficient route, although from @Crespyl's comment I'm guessing that it can be brute forced as well.
Part 1
Part 2
Due to past experience from Lanternfish and other exponential problems in the past, I decided to simply save their multipliers in a table instead of making actual copies of the cards.
I'm taking this year as an opportunity to try something new and using Haskell. Really enjoying it so far, though I need to check out how regex works as my current parsing could be improved. After going for the leaderboard in years past with python, just messing about has been much more fun. If you have any Haskell tips I could benefit from, let me know!
Solution
Language: Python
Part 1
Sets really came in handy for this challenge, as did recognizing that you can use powers of two to compute the points for each card. I tried using a regular expression to parse each card, but ended up just doing it manually with split :|
Part 2
This took me longer than I wished... I had to think about it carefully before seeing how you can just keep track of the counts of each card, and then when you get to that card, you add to its copies your current count.
GitHub Repo
As others have said, it's a pretty simple one today.
I redid part 1 of day 1 with nom and then decided not to bother with it any further if I don't have to.
I had forgotten how annoyingly opaque its errors are.
Rust
Language: Julia
Today was surprisingly simple for some reason, especially compared to yesterday
Solution
The code could've probably been shorter if I used maps and generators and other functional trickery, but whatever, it's short enough as it is.
Im really coming to love nom for parsing. Also really proud of my solution performance wise, I only have to solve each card once, dont have to solve any of its copies.
https://github.com/gabevenberg/advent-of-code-2023/tree/main/days/day04
Dayjob made me put this off until after work and other responsibilities. Kinda messy. Part 2 had me stumped for a while. I conceptually knew what to do, but couldn't figure out what to type into IntelliJ.
Java Solution
Rust
Data structures and parser
Part 1
Part 2
What I've got so far is up on GitHub.
Guess I shouldn't have started when it was already almost time for the next challenge to come out...
Attempt 1
Optimization
My optimized version runs ~2000 times faster than my initial version, so that's nice.