16
votes
Day 5: Hydrothermal Venture
Today's problem description: https://adventofcode.com/2021/day/5
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>
Part B was a bit finicky, but this was pretty entertaining. I feel like Advent of Code has a problem like this every year. I love whenever I can break out regular expressions
Day 5 Part A - Python
I used a nested default dict like @spit-evil-olive-tips recommended the other day. It worked great for handling my grid and making it so I could expand it. And I build a regular expression the grabbed the x and y coordinates and walked through them incrementing the values of each coordinate it passes. Then just count up anything with more than vent at a given location
Day 5 Part B - Python
My original method worked well enough that I just had to add walking through diagonals. It was pretty simple conceptually, but a bit tedious to get all the x1 vs x2 and y1 vs y2 and +/- and what not in the right order.
Tips
The text parsing required here is a bit more sophisticate than what we've been using. If you're not already familiar with them, regular expressions (AKA Regex) are a very handy approach to this
Pay special attention in part B. If you run into problems it may help to walk through the path one step at a time and make sure it's travelling at the correct angle
Python golf, day 5. Tips and improvements always appreciated.
Part 1
Part 2
Python
This reminds me a lot of 2019's Crossed Wires.
Part 1
Part 2
I liked today's challenge overall. I actually had another solution initially, but when part 2 went like this, I decided to rewrite it for fun.
Old Part 2
Phew. Part 2 took me longer than I would like to admit. Turns out, when you
specify a parser that relies on a newline at the end of the input, you'd better
include a final newline in your test case...
As usual for me, the solution is in Rust.
Setup & Parsing
Writing a parser here is probably overkill, but I wanted to further familiarize myself with
nom
.Helper functions
This returns a vector of all coordinates in a line. I like the use of
clamp(-1, 1)
here. It's a bit unfortunate that this returns a vector; I should try to convert it into an iterator if I find more time to work on this.Edit: Here is what I came up with to return an iterator. I don't quite understand why the trailing
+ '_
is necessary here. Ah, the mysteries of the borrow checker...Iterator version
Part 1
This filters out all diagonal lines using
drain_filter
and then counts the number of times each coordinate appears in the remaining lines using a HashMap.Part 2
This is very similar to part 1, except I'm not filtering out diagonal lines.I just want to say that I completely forgot about
drain_filter
, it would've been super useful for me for yesterday's code. Thanks for reminding me of it.Our Rust implementations are pretty similar, except I ended up using a Vec to store the final vent map in the end rather than a HashMap. Kind of forgot about HashMaps despite using them for this very purpose in previous years. I think I'll refactor mine to use as HashMap like yours later.
Funny, I refactored mine into a Vec (kind of...) because the HashMap was slow... ^_^
Code / Spoilers
The idea was to insert all coordinates into a Vec, sort it, and then count the number of duplicates — I was not sure whether sorting or hashing would be faster.
However, the HashMap ended up being faster, and then I ran out of patience and did not try your approach of nested
Vec
s...I'll have to steal the signum function though. And here I thought I was being clever when using
clamp(-1, 1)
...Quick question, when I try to run through your code for Part I I'm hitting errors for your line
As someone who's using this project to learn Rust, I feel like this error message is probably something obvious that I'm missing?
My initial guess was related to how I'm declaring
vent_map
, but I'm doing the same thing you are.Are you including the derives on the line above the struct definition?
Those tell the compiler that it can make the standard assumptions for hash and eq.
Ahh, nope, that was it, thanks! Today was my introduction to derive, so I'm not surprised I missed it.
That's cool to learn!
This is similar to Crossed Wires from a few years back. For those of us working in spreadsheets, its possible -- but not easy. Most people end up with 8000+ formulas.
I knew this day would come, but I'm taking my first day off. :)
Much like many others, I suspected what part 2 would be after reading part 1. But then I didn't do anything to prepare for it to happen, so I ended up needing to do some modification and a little debugging because I thought I could swap the points around when making the lines but that was not the case for all diagonal lines if I wanted them to be actually correct. Also learned that you have to define your own hash function if you want to use a custom type as a dictionary key in Nim, which is interesting, but I guess it makes everything more explicit. Looking at the other solutions here it looks like making a Point type and using it for things probably made my solution longer and possibly slower than it really needed to be but it does the job.
Part 1
Part 2 diff
I like how you used
SPOILERS
a HashMap.I iterated through every single point in every single line to find the maxes and then set up 0-filled vectors for my grid. While it's not super complex to write (not that the HashMap is either), it's definitely slower than your implementation...
Racket
Notes
I guess most of us have a few procedures bundled up into a small library that are useful for AoC problems and take the pain out of having to do really common tasks.
Mine are:
read-input
- convert the input into a list of strings.map-input
- same but run a procedure against each line.words
- split a string on white-space.numbers
- extract only numbers from a string into a list.I find it fun to parse complex input, but not today.
vents
is a hash-table from co-ordinate(list x y)
to count.*Part 1 ignores diagonal lines, so I decided to write 2 plot procedures for orthogonal and diagonal lines. To support those I wrote the
in-plot
sequence which goes either forward or reverse though the range taking care to include the end point.It feels wrong but since one of the axes won't change for the orthogonal plot - it turns out tidier writing nested iterators (using
for*
) one of which only iterates once. This meant I didn't have to write any fussy logic to check which axis changes.hash-update
is nice - you give it a procedure to update an existing key's hash value (add1
here) and either a default value (0 in this case) or another procedure that generates the default value which gets inserted into the table if the key is missing.Update:
* - I was a bit disappointed at the time of each part of my answer. The obvious choice was to change the hash table to a 2d array (using vectors). This wastes a lot of space but is faster. I need to find the maximum co-ordinate from the input to generate the array. At the end I scan the whole array to find the dangerous vents.
hash table - averaging out over 50 runs:
Part 1: cpu time: 102.4 real time: 102.4 gc time: 25.4
Part 2: cpu time: 232.4 real time: 233 gc time: 62.6
2d array - averaging out over 50 runs:
Part 1: cpu time: 16.58 real time: 16.58 gc time: 1.74
Part 2: cpu time: 22.22 real time: 22.3 gc time: 1.72
lol: more time spent garbage collecting with the hash table than the runtime of the array.
Part 1
Part 2
The interesting thing about
plot-diagonal
is that it iterates through both axes in parallel (usingfor
) as opposed to nested (usingfor*
)Tips
Anything involving a 2d grid usually has y increasing down the screen.
For some reason my gut instinct is to iterate x values first. For AoC I generally iterate through increasing y values in the outer loop and then nest the x values. That way any debug output gets output in the same direction as the displayed examples.
Rust
Edit: Updated code to use a different approach for storing the map for part 2 just for the novelty.
Rust
Wow, I was definitely expecting fewer people saying part 2 was difficult. The diff between my part 1 and part 2 is literally 2 lines, I just removed the validation check I put on the input step. Though to be fair, I suspected the twist right away when the puzzle said to ignore certain inputs... ;)
Part 1
Part 2
Late to the party. I've been learning Rust for this, as well as getting back into programming, which I haven't really done much of for ~2yrs. Languages I've been good at in the past are python and go. So I am a little behind, just finished day 5 today. (Also, etiquette Q - should I just blast all the prior threads with my solutions there? I feel like not, but I want people to see them/give feedback on them...)
Related: Feedback very much appreciated. I kinda hate the way I got the diagonal lines in, but don't know a better way. Rust ranges can't have a negative step, you have to do
(1..100).rev()
instead of(100..1).step_by(-1)
, and I don't know how to do this neatly. The python code I want to write isrange(start[0], end[0], step_x)
. A match with 4 separate arms maybe? The vertical/horizontal lines piece is so clean in comparison...I originally had each segment represented as a HashSet of points, and then a nested for loop checking the intersection of every combination. This worked but was slowwww. The person mentoring me walked me through figuring out a more performant solution. Another thing that would be neat to hear from other people is: given a working solution, what steps do you take to arrive at a performant solution? What I ended up with was collecting all the points from every segment, sorting them, and then getting ONLY duplicates, then deduping and counting that. I would love to know if there's a functional way to do that - for an iterator, keep a value if it's equal to the prior value. I thought this looks kinda like a fold? That's an operation that keeps state, and I could just set the accumulator to the prior value, but then there's no filter predicate... I could also use the nightly
(_, duplicates) = vec.partition_dedup();
and then sort, dedup, filter, but I have been avoiding nightly-only functions because I figure there's probably a better way to do it.Other little questions:
impl
on a type alias, so I can doSegment::new
instead ofnew_segment
?Iterator<Point>
or something... what would that look like? Quick search for how to return an iterator seems complicated.[i32; 2]
vs a(i32, i32)
vs astruct Point { x: i32, y: i32}
?Anyways, here's what I came up with. I will look at other answers in this thread after posting.
Rust
Unfortunately, Rust's ranges are horribly anemic. If you want to do something like that, you'd probably write your own custom iterator for it. One workaround in this case could be
(1..100).map(|&x| 100-x)
or something similar. Still ugly, but it gets you what you want.This is a tough question, but it essentially condenses down to "eliminate any factors that are not needed, or group them together". For example, in the lanternfish problem, it's tempting to model the lanternfish as their own object, but you only really want to know one thing about each one.
Are you restricting yourself to just the Rust stdlib? If not, I'd suggest learning about some common crates that can be useful for development. In this case, I used the
itertools
crate, which should be kind of familiar if you have experience with Python. In particular, thecounts()
extension method may be what you wanted (with an additional filter operation).If you want to create your own iterator to do that, it would be a good exercise, as well.
On a type alias, no. You'd have to create a newtype for that.
Here's a sample, at least for this challenge's needs. Note that I'm doing this on my phone, so there may be errors:
Rust vent point iterator
Basically, the iterator returns
None
when there's nothing left to do, andSome(x)
when you want it to returnx
. Generators are going to be in Rust soon, in case you miss having an explicityield
keyword.Vec<i32>
than an alternative to a tuple in this kind of case, IMO.(i32, i32)
for when you just want the point information as a single data structure, and want to make a quick or simple implementation.struct
for when you want to implement methods for the data and have nicer access to it. It can also have traits that lets you convert it into the other two forms easily, if needed.In this challenge, considering that the points don't need to have any methods on them in this case, I'd either lean towards a tuple, or a struct with a
#[derive(PartialEq, Eq, Hash)]
on it. (My personal solution when I wanted to get this done was the tuple, but going back and making it a struct is definitely something I might do.)Hi! Thank you SO much for the detailed reply. Lots to think about, & I will respond properly.. at some point. Work this week is very busy with holiday orders, so I'm not sure when I'll next have the bandwidth to go through all this but I wanted to say some words to let you know I really appreciate the help.
Alright I'm working on Part I in Rust and have hit a snag.
Right now my plan is to basically build a grid as I go, and for each coordinate I'll update my grid with all the spots it hits.
The problem I'm having right now is basic, some coordinates the x/y values increase, some they decrease. I was hoping I could basically do
but it seems like the language doesn't like that when start is bigger than finish. Is there an easy way to do this or will I just need to do some sort of check and reverse them?
Today was cool! My implementation is definitely not as efficient as possible but I think I'm finally figuring out how to do things in a way that's a little more idiomatic to Rust (which in turn looks 1000x nicer than some of my earlier days).
Rust (both days)
Structs
"Main" file
Today took me a while because I wanted to implement the
iter()
method on a line and to do it in a way that would be compatible with the rest of the standard library (that is, to haveiter()
return a struct that implements theIterator
trait) Thankfully I was able to do it! I ended up writing a big utility module calledgeometry
that does most of the heavy lifting; the actual main file is tiny by comparison!https://git.sr.ht/~blitz/advent-of-code-2021/tree/master/item/day5
Whoops, I forgot to post my solution to this one on the day, but I'll post it now anyway for posterity.
This was another one where the set up in the first part made the second part pretty easy (I actually got my part 2 answer first by accident, forgetting the "ignore the diagonals" bit).
Part 1 Ruby
Part 2 Ruby
Part 2 is identical to the first part, except the statement to skip diagonal lines is commented out.