Starting a new comment thread for my solutions to 10-19. Double digits, baby! Code here: https://github.com/Fluxward/aoc2023/
NotAwfulTech
a community for posting cool tech news you don’t want to sneer at
non-awfulness of tech is not required or else we wouldn’t have any posts
- I have contracted some kind of sinus infection, so rn I have a -20 IQ debuff.
a,b
part a: nothing to say here.
part b: Before diving into the discussion, I timed how long 1000 cycles takes to compute, and apparently, it would take 1643175 seconds or just over 19 days to compute 1 billion cycles naively. How fun!
So, how do you cut down that number? First, that number includes a sparse map representation, so you can tick that box off.
Second is intuiting that the result of performing a cycle is cyclical after a certain point. You can confirm this after you've debugged whatever implementation you have for performing cycles- run it a few times on the sample input and you'll find that the result has a cycle length of 7 after the second interaction.
Once you've got that figured out, it's a matter of implementing some kind of cycle detection and modular arithmetic to get the right answer without having to run 1000000000 cycles. For the record, mine took about 400 cycles to find the loop.
a,b
I took a very similar approach to parts a and b, with the difference that i was too lazy to do titling in each direction, and wanted to abuse regex so Instead i always titled up and rotated, which given my method of tilting up and rotating had some satisfying cancelling of transpose operations:
https://github.com/zogwarg/advent-of-code/blob/main/2023/jq/14-b.jq
# Relevant portion
# oneCycle expects an array, of array of chars (3x3 eg: [[".","#","."],[".",".","."],["O",".","#"]])
def oneCycle:
# Tilt UP = T . MAP(scan) . T
# Rotate = T . MAP(reverse)
# Titl UP . Rotate = T . MAP(scan) . Map(reverse) | T . T = Identity
def tilt_up_rotate:
transpose # Gets subgroups # Within each group,
# starring with "#" # In order 1 "#", x "O", y "."
| map( ("#" + add) | [ scan("#[^#]*") | ["#", scan("O"), scan("\\.")] ] | add[1:])
| map(reverse)
;
# Tilt North, West, South, East
tilt_up_rotate | tilt_up_rotate | tilt_up_rotate | tilt_up_rotate
;
JQ does allow some nice sortcuts sometimes, again transpose
is nice to have.
neat!
I need like 25 more IQ points before I think of using a transpose in any context
a,b, not much to say
The hardest part has finding the right dart ascii library to use (by default dart treats everything as UTF-16, which is horrible for this sort of thing) and the right data structure (linked hash map, which is a map that remembers insertion order.)
- After this problem, I will create a new reply in the OP if it is not there already, and will discuss under that thread.
a,b
So, like many other problems from this year, this is one of those direct solution problems where there isn't much of a neat trick to getting the answer. You just have to implement the algorithm they specify and hope you can do it correctly.
a) I used a regex to do some parsing because I haven't looked at dart regex much and wanted to dip my toes a little.
I considered doing this "properly" with OO classes and subclasses for the different rules. I felt that it would be too difficult and just wrote something janky instead. In hindsight, this was probably the wrong choice, especially since grappling with all the nullable types I had in my single rule class became a little too complex for my melting brain (it is HOT in Australia right now; also my conjunctivae are infected from my sinus infection. So my current IQ is like down 40 IQ points from its normal value of probably -12)
b) There may have been a trick here to simplify the programming (not the processing). Again, I felt that directly implementing the specified algorithm was the only real way forward. In brief:
- Start with an "open" set containing a part with 4 ranges from [1, 4001) and an "accepted" set that is empty.
- Start at the workflow "in"
- For each rule in the current workflow:
- If the rule accepts part of the ranges in the open set, remember those ranges in a closed set and remove them from the open set.
- Remove anything the rule rejects from the open set.
- If the rule redirects to a different workflow W, split off the applicable ranges and recurse at 3 with the current workflow as W.
- Keep in the open set anything the rule doesn't consider.
Because this is AOC, I assumed that the input would be nice and wouldn't have anything problematic like overlapping ranges, and I was right. I had a very stupid off by one error that took me a while to find as well.
The code I have up as of this comment is pretty long and boring, I might try clean it up later.
update: have cleaned up the code.
Replying in OP: Yeah, Lemmy punishes old threads/posts a bit too much for my taste ^^.
Good note for next year!
16
So, as I've been doing the AoC things, I've been creating small libraries of convenience functions to reuse, hopefully. This time, I reused some things I wrote for problem 10, which was vindicating.
a. was a fun coding exercise. Not much more to say.
b. I lucked out by making a recursive traversal function for a), which let me specify the entry and direction of where my traversal would start. Besides that, similar to a., this was a fun coding exercise. I was surprised that my code (which just ran the function from a) on every edge tile) It only took 2s to run; I thought I might need to memoize some of the results.
16 a,b
Neat!
In my case it was a lot more of headbanging, the traverse function i wrote for part a was way to slow, since JQ isn't happy with loop-heavy assignments (if not buried within C-implemented builtins). Part a completed in ~2seconds, which was never going to do (in hindsight it would have taken me less time to simply let it run slowly), I had to optimize it so that the beams don't step one square at a time, but shoot straight to any obstacle.
It took me waaaay too long to troubleshoot it into something that actually worked. I'm sure there's a compact implementation out there, but my part b ended up looking very meaty (and still took ~30s to run): https://github.com/zogwarg/advent-of-code/blob/main/2023/jq/16-b.jq
That’s such a different programming paradigm than I’m used to!
10!
a, b
a was relatively straightforward - again, an implementation test.
b was interesting- luckily, I remembered how to test if a point lives inside a curve. The line-crossing count algorithm was a little tricky to write from scratch, but I finally got there. Also, I screwed up for like an hour when I didn't realise you were supposed to count junk pipes as part of the territory. Code wise it got messy, and I've thrown something up on my github, but might try clean it up a little later.
Update: I've cleaned up my code and made it "dartier" i.e. I use more neat dart syntactic sugar at the cost of readability.
17, We’re in the back third now, folks!
a, b
A and B were roughly the same difficulty.
So in my first year in university, in my intro DSA class, we learned A*. Have I learned any other ways to search since? Not really. So I used A* here.
It took way longer than it should have to solve, which I blame on my ongoing illness. The main sticking point was that I implemented a class to represent the search state, and since I was going to use it as a key to a map, I implemented a hashcode for it. The rest of the A* code freely flowed from my brain (really the wikipedia pseudocode).
Cue like 40 mins plus of wondering how the test input was searching over millions of states, wondering if I’d fucked up the A* implementation, wondering if the problem was too big for A*, and wondering if it was finally time to take a days break from all the aoc nonsense.
Anyway at some point I realised I forgot to implement the corresponding equals method to my hashcode. Once I had that my code ran in seconds and everything was fine. This sickness is the worst!!!
Day 5: If You Give A Seed A Fertilizer
https://adventofcode.com/2023/day/5
Leaderboard completion time: 26m37s, so it's the toughest problem this year so far.
Day 6: Wait For It
https://adventofcode.com/2023/day/6
Alternate spoiler name - for part 2
~~Do you remember highschool algebra?~~ Can you (or your compiler) remember highschool algebra fast enough to beat out a naïve implementation?
nice cleanser after yesterday
spoiler
it would have taken me longer to figure out the algebra than to just mush the inputs together and get the solution that way (16s runtime)
I have come up with a more horrifying way to solve Day 1 Part 1 involving C++ implementation defined behavior, but it requires launching two subprocesses for every test case so I'm not sure I have the motivation right now.
Proof of concept: https://www.animeprincess.net/blog/?p=60
Day 7: Camel Cards
https://adventofcode.com/2023/day/7
So far, my favorite puzzle. Challenging but fair. Also plays to Perl's strengths.
Leaderboard completion time: 16 minutes flat, so not a pushover.
Day 8: Haunted Wasteland
https://adventofcode.com/2023/day/8
Not so easy at least for part two.
spoiler
Do you remember high school math, like lowest common multiple, part 2 electric boogaloo.
Day 9: Mirage Maintenance
My solution: https://github.com/gustafe/aoc2023/blob/main/d09-Mirage-Maintenance.pl
discussion
What can I say. Shockingly simple.
I just literally followed the instructions, and got a solution in 20ms. This despite literally creating each intermediate array yet only using the ends. I'm sure I used way to much memory but you know? I'm using a $5/mo VPS for everything and unless I'm barking totally up the wrong tree I've never exceeded its memory limits.
On the subreddit I see people discussing recursion and "dynamic programming" (which is an empty signifier imho) but I really don't see the need, unless you wanna be "elegant"
Day 20: Pulse Propagation
It feels weird to kick one of these threads off, but hey, here we go.
Code as always: https://github.com/Fluxward/aoc2023/blob/main/20.dart
a,b
A
So following from yesterday where I was punished by not going full OO, I decided, hey, this is a problem in which I can do some OOP, so I did. This took very long to do but I regret nothing. If you look at my code, feel free to click your tongue and shake your head at every opportunity I missed to use a design pattern.
Anyway, after a slight snafu with misunderstanding the FF logic and not spotting that some modules can be dummy modules, it all just worked, and I got my answer.
B
This was a bit of a headscratcher, but the answer was surprisingly simple.
First, the answer. Here's how to do it:
- Look for the "rx" module in your input.
- If the module that outputs to rx is a conjunction, keep track of how many button presses it takes for each input of the conjunction to change. The answer is the LCM of all those numbers.
- If the module is a FF, you can also work backwards to get it, but this was not my input so I didn't need to try this.
Getting here was a bit weird. I hoped that I could just run the code from A and spit out the answer when rx went low, but as of time of writing I've been running it now on a separate machine for about an hour and still no result.
My next instinct was to simply work it out from pen and paper. I thought it might be possible (it probably is) but decided to at least experimentally see if the states of the modules connected to rx were cyclic first. I did, and that was enough for me to get to the answer.
My answer was about 230 trillion BPs, which, extrapolating on how long execution is taking on my other machine, might take just under 137 years to calculate naively. Fun!
Completed when waiting for the second leg of my Christmas holidays flight. (It was a long wait, can I blame jet-lag?).
Have a more compact implementation of LCM/GCD, something tells me it will come in handy In future editions. (I’ve also progressively been doing past years)
21 Step Counter
Starting this thread having only solved a.
A
Pretty straightforward. Could probably be done in a few lines with the right syntactic sugar.
B
This is some game of life thing that I've never implemented or seen an implementation of, so I am altogether lost.
My current code (https://github.com/Fluxward/aoc2023/blob/main/21.dart) has a memoisation based approach but my current ailments are preventing me from really thinking about this properly so I am bowing out until I have the wherewithal.
This is the hardest problem of the year so far, based on leaderboard completion times. I'm busy wrapping up work for this year, and looking for a new job, so this will have to be put on the TODO pile
At this point I have officially given up and started looking at other people’s code. I’ll work on it after Imm fully better, it’s too much for me right now.
Only solved by receving heavy hints from other's solution, and it still took me forever. By far the hardest this year.
Update on B:
still no solve, however
Through glancing at someone else's code, I was inspired to just try simulating the A problem beyond 64 steps and seeing the result.
Essentially it reaches a (bi stable?) steady state between two numbers, which makes sense- if you can only make single rook steps, then the reachable squares will alternate every cycle.
Don't know if I'll try solve this again tonight but mentally I have now understood the solution.
Happy Holidays everyone. I’ve decided I am going to take a break from aoc to properly rest and recover from my mystery illness. Perhaps I will attempt solves again in the new year.
Happy holidays to you too! I decided this morning that I'm not gonna work myself up missing days, so they are on hold until after xmas for me!
Get well soon!
Happy holidays!
Day 12: Hot springs
https://adventofcode.com/2023/day/12
- Leaderboard completion time: 22:57
- Personal completion time: ahahahahahahaha (at least i had fun)
Where a curse the fact I decided to use JQ and not a "real" programming language.
spoiler
Had to resort to memoization, but sadly JQ isn't mega well suited to that. I had to refactor my part 1 function, to make including the "state" at every function call possible. I wish it were as easy as a @cache
decorator, but i guess this way i had fun (for an arbitrary definition of "fun")
Further cleaned up version: https://github.com/zogwarg/advent-of-code/blob/main/2023/jq/12-b.jq
Also lost a fair amount of time not not noticing that the sequence should be joined with "?"
not with ""
. (that'll teach me to always run on the example before the full input, when execution time is super long).
Execution time: 17m10s (without memoization a single row was taking multiple minutes, and there's 1000 rows ^^...)
EDIT: see massive improvement by running in parallel in reply.
A nice workaround to jq single threadedness, since this is maq reduce and safe to parallelize. 17m10s -> 20s !!!
Spoiler link to commit.
https://github.com/zogwarg/advent-of-code/commit/fef153411fe0bfe0e7d5f2d07da80bcaa18c952c
Not really spoilery details: Revolves around spawing mutiple jq instances and filtering the inputs bassed on a modulo of number of instances:
# Option to run in parallel using xargs
# Eg: ( seq 0 9 | \
# xargs -P 10 -n 1 ./2023/jq/12-b.jq input.txt --argjson s 10 --argjson i \
# ) | jq -s add
# Execution time 17m10s -> 20s
if $ARGS.named.s and $ARGS.named.i then #
[inputs] | to_entries[] | select(.key % $ARGS.named.s == $ARGS.named.i) | .value / " "
else
inputs / " "
end
I use JQ at work, and never really needed this, i guess this trick is nice to have under the belt just in case.