13
votes
Day 4: Giant Squid
Today's problem description: https://adventofcode.com/2021/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>
Python
Repo Link
Part 1
Solution was straightforward... it was just a matter of reading the board and performing the appropriate operations. One trick I used to make checking for BINGO easier was to mark cells with
-1
and then I could just sum up the row or column and check if it was-5
to see if there was BINGO.Part 2
Only my main function changes here. Instead of looking for the first board, I simply remove boards as they win and just keep track of the last winner.
This one fits nicely into that sweet spot where creating an object/class for tracking the boards makes the rest of the logic pretty easy to follow. The built in
Set
type comes in handy here too.Board class
Part 1 Ruby
Part 2 Ruby
Extra discussion
This is pretty inefficient, Part 2 is especially slow (~0.2s on my machine). I think a lot of that is in how I'm checking for wins on each board. Right now I'm always revisiting all the rows to find the numbers in each column, and then making a
Set
out of them. If performance was a bigger issue I might do something like pre-compute the numbers in each row/column, or actually remove numbers from the board once they get marked so that we have fewer things to check as the process goes on.ok, I did this one -- but I'm not 100% certain that part 2 will work for all datasets.
Check this sheet if you want to see some magic
There are some fancy tricks in there -- like a single formula to transpose individual blocks of data, etc.
Most formulas are on the second row. I tested it with two datasets and it worked both times, so that's good enough for me. The part 2 bit in AO5 is sketchy, though.
Hey, good to see you're back and doing this again! I really enjoy checking out your spreadsheet implementations, even if I don't quite understand fully what's going on. :)
Are the functions/features you're using specific to Sheets, or is all this logic able to be run in Excel as well?
thanks! I was up so late last night working on this beast. In typical fashion, I woke up a few hours later and rewerote the formula for part 2 so its more logical.
Most of it can translate with a few changes. The major difference (unless something has changed recently) is the
ARRAYFORMULA
function instead of using ctrl+shift+enter.I'm wondering if topaz and crew took spreadsheets into consideration this year. So far everything has been fairly straightforward for us who don't know anything else.
I think I made part 1 a lot harder than it needed to be, but for whatever reason, my part 2 only took about 30 seconds. I also fucked up by making my board checker look for diagonals. Don't do that, these boards don't use diagonals.
Day 4 Part A – Python
I just converted each board into a list of numbers and slowly checked them off. Then I made lists of indicies and checked if they were all called since the possible winning combinations are fixed and known. When a given board completes, write down the number of calls remaining and calculate the score. Then print out the score of the board with the most calls remaining. I REALLY didn't want to try to manage each board individually while walking through the list since I figured part 2 would throw a horrible wrench into my plans if I did that.I struggled a bit with getting check boards to work. I was trying to get all() to work for a while, but eventually just gave up and chained some == together. I also forgot to multiply my indices at first, so that made my horizontal check break. Also as I mentioned before I implemented diagonals. That fucked me up for a bit.
The hoz_check, diag_check, vert_check aren't used. They're remnants of an earlier attempt. I realized they weren't really saving any time or space
Day 4 Part B – Python
Literally just swapped out the max in the last 3 lines for min and it worked for part B. Huh. Okay.Tips
Make sure to read the descript of the problem. In this case it doesn't do diagonals which can cause problems.
You'll need to think about how to store the data for a given board in a way you can work with it. Fortunately boards are all a fixed size and the ways they can win are fixed
For me building a function that let me visualize the status of a given board was very useful in debugging
Rust
Edit: Code fixed. Turns out the way I wanted to slice the cards wouldn't work.
If anyone knows why the commented out portion of my
validate
function doesn't work, pls let me know. I was getting an error that I couldn't compare&bool
tobool
, but no amount of referencing/dereferencing the bools would work, so I feel like there may be some other problem with what I was trying to do.Rust
Wow, I didn't know you could iterate over 2D vectors like that. Thanks for sharing!
I fiddled a little with the validate function and came up with the following:
Rust code
.iter()
gives you references to the data (unless you call.copied()
on it, which will clone everything). You can add an ampersand before thex
in the closure definition, and it will give you a dereferencedx
. That kind of pattern matching is neat. Lastly, the comparison ofx == true
is redundant if x is a boolean, because it itself will evaluate totrue
orfalse
.Edit: Hm. I looked at it a bit more, and even a simple dereference (
*x == true
) worked for me. Tried it in both stable and nightly rust, so I'm not sure why it wasn't working for you :/I swear I tried
.all(|&x| x == true)
before and it wasn't working, but now it clearly is so I don't know what I thought I was doing before. 😅 And I feel like a buffoon for actually writingx == true
--I'd certainly never do that in anif
statement, so I don't know why I thought I had to do it in.all()
. ThanksFor whatever reason, though, with this change I now get the wrong answers. Puzzling. Maybe you really can’t slice Vecs this way.
Rust
I can probably guess -- just
|x| x
gives a compile error (due to the same issue), so you probably tried something "obvious" to compare it.Here's my solution in Rust. I opted to use an actual parser,
nom
, to parse the puzzle input. It's kind of verbose, but not too bad.After parsing everything, the remainder was almost trivial.
Setup & Board parsing
Helper methods
Part I
Part II
As an interesting side-note: when I got the solution wrong in part II (did not mark the won boards as won at first so they were double-counted...) it told me the solution was wrong, but it would be right for someone else, and told me to check if I was logged into the correct account... I wonder what the chances are for that.
Adding my anecdote to yours, I got that response on one of the problems last year. Because everyone's answer is numeric and since the puzzle constraints put a likely upper and lower limit on the possible answers, I think the chances of this would actually be pretty high. I would expect this mostly happens when reading or interpreting the input goes horribly wrong, but your solution to getting this response seems pretty creative as well. :)
Racket
Notes
Racket has classes but for AoC if I need to maintain state I'll generaly use a struct.
So my bingo card is:
The Racket compiler automatically generates a bunch of useful procedures for structs.
e.g.field selectors (
card-grid
,card-marked
, ...) and mutators if any fields or the struct is marked#:mutable
(set-card-marked!
,set-card-winner?!
)Scheme has a convention where mutator procedures end in "!" and procedures returning a boolean end in "?. It pleases me too much use ? for boolean fields too so I get procedure names like
set-card-winner?!
...yeah I'm easily amused.Input parsing.
Racket's iterators are built on
for
forms and sequences.After extracting the game numbers, I removed blank lines and grabbed the card definitions.
in-slice
is an iterator that groups a sequence by a specific count.Nice! saves me that drudgery.
The only thing left is to convert the numbers into a card.
card-grid is a hash table from number -> grid co-ordinate to help check for a bingo later.
The co-ordinates are generated using
in-indexed
- like Python'senumerate
.Part 1
for/first
returns the value when the body first returns anything that isn't#f
- false.The card struct has a list of the numbers called that are on the card.
That way it's easy to sum them and subtract from the card's total.
The only thing left is to check for a line of matched numbers.
I extract the co-ordinates of the marked numbers and group them by row and by column.
If any group has 5 members I mark the card as a winner and return #t.
(I only added the winner? field after I read part 2.)
Part 2
I added the
winner?
field to the struct after reading Part 2 and set it whenbingo?
is true, which made it trivial. All I do is ignore complete cards and instead of usingfor/first
I usefor/last
I lost a lot of time today head-scratching because I converted the card's co-ordinates to numbers but forgot to convert the first line to numbers 😃💩.
After smugly quoting Linus about data structures, I'm ashamed at the inefficient check for a winning card so I threw some memory at it.
Changes
Add a count for each row and column marked in a hash keyed on "r<num>" and "c<num>". Bingo is when any count is 5.
Python
Compared to day 3, this one was easier and actually kind of fun to implement. I was quite happy with the change from part 1 to part 2. I was expecting a different twist, so my implementation only required minimal changes to get the new answer.
Part 1
Part 2
Python golf, day 4. This was a pretty poor golf on my part (at least compared to yesterday), but I'm calling it a day here.
Part 1
Part 2
I really enjoyed today's problem(s)! I think my code isn't too terrible for once.
Rust (both parts)
My solution only works with a 5x5 grid but I wanted to try out tuple structs.
I cleaned up my solution for today. Not really my best Rust code (pretty brittle, assumes a lot of things about the puzzle) but it does the job well.
Solution
There's a lot I don't like about it, like the
transpose
call. It's slow, but it looks nice, at least.Today was a good day for me to actually try and do things right and maybe define some functions and use object oriented programming! Much like others on the thread, my part 2 was not too difficult to add in once part 1 was complete thanks to the preparation. Working through these I have started to notice some things I am not loving about Nim. Not a huge gripe, but something that irked me today is that the
all
function for checking if everything in a sequence meets a condition always requires aproc
to work, and it won't work with sensible default on a sequence of boolean types. Anyway, parts 1 and 2, with 2 as a diff since I added it into things:Part 1
Part 2
Elixir, with a message-passing/agent example
I decided to define a "BingoPlayer" Agent to create an API around updating and querying each bingo board. This is Elixir's version of object-oriented programming—it's closer to the original sense of the term used by Smalltalk, for example. "Objects" are Elixir processes running send/receive loops, which communicate with each other exclusively via message passing. It's comparable to a server/client relationship, just running within the confines of your own application. The Agent module provides an abstraction over this to simplify setting up a stateful process.
One part I'm not happy with is the bingo-checking code. It's an unreadable mess! I think a list comprehension would have been better for generating all the possible rows/columns to check, but my brain was a little fried at the time.
BingoPlayer
agentSolution, both parts
Solutions in Go.
So far I haven't included the code that reads the data, I still won't include but to make the code more clear I will start typing the
ReadData
results.Part 1
Part 2
Like everyone else here, I was really happy to see that part 2 was pretty easily implementable if you set up the data structures in the right way!
I did the problem with arrays of arrays just to see how they differed from Vecs, though I know that the actual use cases for owned arrays are scarce in Rust.
Day 4 in Rust on Sourcehut
This was a lot simpler after I realized that diagonals were not included.
Part 1
Part 2