- 411
- 769 692
Jonathan Paulson
United States
Приєднався 29 вер 2011
Advent of Code 2024 Day 25
I placed 60/48. I placed 61st overall this year.
Problem: adventofcode.com/2024/day/25
Solution: github.com/jonathanpaulson/AdventOfCode/blob/master/2024/25.py
Pretty easy puzzle today; just classify each shape into key or lock, and then check if each key fits in each lock. You don't need to count column heights to see if a key fits into a lock; just make sure they don't have a "#" on the same a square.
If you're looking for more Advent of Code-like puzzles, I enjoyed everybody.codes/ which ran this November.
Happy holidays everyone!
Problem: adventofcode.com/2024/day/25
Solution: github.com/jonathanpaulson/AdventOfCode/blob/master/2024/25.py
Pretty easy puzzle today; just classify each shape into key or lock, and then check if each key fits in each lock. You don't need to count column heights to see if a key fits into a lock; just make sure they don't have a "#" on the same a square.
If you're looking for more Advent of Code-like puzzles, I enjoyed everybody.codes/ which ran this November.
Happy holidays everyone!
Переглядів: 1 081
Відео
Advent of Code 2024 Day 24
Переглядів 1,7 тис.7 годин тому
Placed 82/364. Problem: adventofcode.com/2024/day/24 Solution: github.com/jonathanpaulson/AdventOfCode/blob/master/2024/24.py Note: the "solution" linked above doesn't actually solve the problem, although it has some useful snippets. Part 1 is pretty straightforward; just evaluate the circuit. Part 2 is very challenging. I didn't do well on it. There's been a pattern of: 1. I don't know how to ...
Advent of Code 2024 Day 23
Переглядів 1 тис.9 годин тому
I placed 226/256. Problem: adventofcode.com/2024/day/23 Solution: github.com/jonathanpaulson/AdventOfCode/blob/master/2024/23.py Graph problem today. I had a bunch of bugs; in part 1 I sextuple-counted triangles and read "contains t" instead of "starts with t"; in part 2 I read "biggest component" instead of "biggest clique". Is there a "correct" way to do part 2? My solution seems to work fine...
Advent of Code 2024 Day 22
Переглядів 88612 годин тому
I placed 91/150. Problem: adventofcode.com/2024/day/22 Solution: github.com/jonathanpaulson/AdventOfCode/blob/master/2024/22.py Pretty weird problem today; you really have to read everything to understand what's going on. It took me a little while to find a fast algorithm for part 2, which ended up being: 1. Compute each list 2. Go through each list, recording the latest 4 differences and the c...
Advent of Code 2024 Day 21
Переглядів 1,4 тис.12 годин тому
I placed 2/1041. Great part 1; terrible part 2. Problem: adventofcode.com/2024/day/21 Solution: github.com/jonathanpaulson/AdventOfCode/blob/master/2024/21.py Part 1 went really well; part 2 did not. For part1, I just did a BFS where the state was (keys typed, keypad1_position, keypad2_position, keypad3_position, output). Tricky to implement all the rules, but worked fine. It took me a long tim...
Advent of Code 2024 Day 20
Переглядів 1,8 тис.16 годин тому
I placed 231/1009. Problem: adventofcode.com/2024/day/20 Solution: github.com/jonathanpaulson/AdventOfCode/blob/master/2024/20.py Tough day for me. I found it hard to understand exactly what the rules for "cheats" were, partially because the examples didn't mark the start position of the cheat. The rules are: - You can start cheating in any normal square. It doesn't cost any time. - When cheati...
Advent of Code 2024 Day 19
Переглядів 2,1 тис.19 годин тому
I placed 191/134. Problem: adventofcode.com/2024/day/19 Solution: github.com/jonathanpaulson/AdventOfCode/blob/master/2024/19.py Pretty straightforward DP for both parts. It's cool that we can calculate the answer for part 2 so fast with memoization even though the number is huge.
Advent of Code 2024 Day 18
Переглядів 1,8 тис.21 годину тому
I placed 284/146. Problem: adventofcode.com/2024/day/18 Solution: github.com/jonathanpaulson/AdventOfCode/blob/master/2024/18.txt Sadly I spent a long time with a 70x70 grid (instead of 71x71) and very confused why part 1 had no path :( More BFS in a grid. For part 2, I just ran ~3000 BFSes, which took ~7s. A faster way would be to do it in reverse with union-find.
Advent of Code 2024 Day 17
Переглядів 4 тис.День тому
I placed 203/11. Problem: adventofcode.com/2024/day/17 Solution: github.com/jonathanpaulson/AdventOfCode/blob/master/2024/17.py Decompiled input program: github.com/jonathanpaulson/AdventOfCode/blob/master/2024/17.txt Very interesting part 2 today! A mix of analysis and brute force. I manually decompiled the program to notice that the base 8 representation of A was relevant and that the outputs...
Advent of Code 2024 Day 16
Переглядів 2,4 тис.День тому
Placed 185/60. Problem: adventofcode.com/2024/day/16 Solution: github.com/jonathanpaulson/AdventOfCode/blob/master/2024/16.py Pathfinding on a grid using Dijkstra's algorithm. For part 2, we need to run the pathfinding in reverse to get all the paths *from* the end, which makes the "forward" moves a little tricky (they become "backward" moves). I had a bug in part 1 (turning included a forward ...
Advent of Code 2024 Day 15
Переглядів 1,8 тис.День тому
I placed 46/4! Problem: adventofcode.com/2024/day/15 Solution: github.com/jonathanpaulson/AdventOfCode/blob/master/2024/15.py Handling multi-push is tricky in both parts today. I did a (simple) BFS for part 2 to figure out the set of squares/boxes being pushed (and whether any of them would hit walls), and then repeatedly looped over them to find a "safe" square to push (i.e. a square that can ...
Advent of Code 2024 Day 14
Переглядів 4,1 тис.День тому
Placed 418/271. Problem: adventofcode.com/2024/day/14 Solution: github.com/jonathanpaulson/AdventOfCode/blob/master/2024/14.py I struggled with the quadrants in part 1 for some reason :( Part 2 it took me a while to find the right criterion for an "interesting" image; my first idea was "all the robots are connected" which was way too strict.
Advent of Code 2024 - Simplifying Day 13
Переглядів 797День тому
I decided to rewrite my day 13 solution to be shorter and faster. Two changes here: 1. Use regex to parse all the integers out of the input, rather than manually parsing each line. 2. Use z3 to solve the system of equations. The resulting code (github.com/jonathanpaulson/AdventOfCode/blob/master/2024/13_2.py) is much shorter and runs much more quickly.
Advent of Code 2024 Day 13
Переглядів 2,7 тис.14 днів тому
Placed 309/1049. Rough day for me. Problem: adventofcode.com/2024/day/13 Solution: github.com/jonathanpaulson/AdventOfCode/blob/master/2024/13.py Part 1 was a straightforward DP (although it probably would've been faster to just brute force it...) Part 2 I wasn't sure how to do. I don't think my solution was very good, because it takes a long time to run and its hard to tell if it gets the righ...
Advent of Code 2024 Day 12
Переглядів 3,2 тис.14 днів тому
Placed 131/26. Problem: adventofcode.com/2024/day/12 Solution: github.com/jonathanpaulson/AdventOfCode/blob/master/2024/12.py I didn't know how to do part 2...but it looks like no one else did either. I'm pretty happy with what I came up with - a BFS on all the "perimeter squares" in each of the 4 directions. Note that when BFSing the up-perimeter-squares, we never move up or down (if its legal...
Stuck on that task in 2021. Did several other attempts in next years, last time was yesterday/today, already after I've finished AoC 2024, and still nothing. It might be that I got stuck with the idea of doing bfs over all positions/scores, or perhaps that's just a skill issue :) Anyway, finally I surrendered and watched this video. Really elegant solution, definitely I need to practice with dynamic programming problems. Thanks, Jonathan!
bro do something about that cough, you have been coughing since day 16 or so... anyway great video as always
Thanks for all the interesting videos. Congrats on making the top 100!
The problem was disappointingly easy - pairwise zip all items and check for presence of (#,#), that is it. No need to preclasify things into locks / keys.
Feel like it was a Xmas gift then! :))
Thanks for the videos and happy holidays!
congrats, and big thanks for all your videos! see you next year ;-) having precomputed set of pin coordinates for multiple reuse and let intersection (&) do the heavy lifting ;-) def solve1(text): blocks = text.split(" ") block_rows = blocks[0].splitlines() R = len(block_rows) C = len(block_rows[0]) locks = [] keys = [] for block in blocks: block_lines = block.splitlines() pins = set((r, c) for r in range(R) for c in range(C) if block_lines[r][c] == "#") if all(x == "#" for x in block_lines[0]): locks.append(pins) elif all(x == "#" for x in block_lines[-1]): keys.append(pins) else: assert False return len([1 for lock in locks for key in keys if len(lock & key) == 0])
It's a Ripple Carry Adder. That's the official term.
gotta just love the manually scanning the tree for things that don't look right, after over an hour trying to figure it out. Hardest part 2 so far. I did part one, and upon reading part 2, not a chance
Anyone can share the flag ?
what is the flag in advent of cyber 2024 day 24
Hi, Can you make a video on how to set up vim like in your computer in windows?
He is using WSL. You can install pretty much anything linux on it
@@harshnj But i saw that installing linux in windows doesn't give full functionality of neovim or something like that Will that be okay?
@@RaviTeja0405 I have not used WSL to that extent, but you may be talking about keyboard shortcuts. That might be an issue, but there will also be a fix for it.
@@harshnj Thanks
I haven’t really done much setup other than installing WSL2.
I've been trying to do this in base 10, turns out it takes a while I guess it all the extra iterations it ends up going through. Thanks for this JP I'm an old hand developer but new to puzzles and I'm pretty sure I'd never of got this one.
With networkx the two parts in total were 6 lines
def solve1(text): E = defaultdict(set) for line in text.splitlines(): a, b = line.strip().split("-") E[a].add(b) E[b].add(a) res = 0 for a in E.keys(): for b in E[a]: for c in E[b]: if a < b < c and c in E[a] and any([x.startswith("t") for x in [a,b,c]]): res += 1 return res
not sure if the best, but I have something like this: def solve2(text): E = defaultdict(set) P = [] for line in text.splitlines(): a, b = line.strip().split("-") E[a].add(b) E[b].add(a) P.append({a, b}) groups = P seen = set() while True: new_groups = [] for set_members in groups: possible_candidates = set.intersection(*(E[x] for x in set_members)) for candidate in possible_candidates: new_grp = set_members | {candidate} key = tuple(sorted(new_grp)) if key not in seen: seen.add(key) new_groups.append(new_grp) if not new_groups: break groups = new_groups assert len(groups) == 1 res = ",".join(sorted(groups[0])) return res
For part 2, I used a floodfill, where I checked if the new vertex is connected to every other vertex in the current component. I am not sure I fully understand why your approach works as well as it does
"where I checked if the new vertex is connected to every other vertex in the current component" That's what I am doing too. But this way can miss the best answer...for example if you have: A-B A-C A-D C-D Then the best answer is {A,C,D} but if you add {A,B} first you won't realize you could've added C and D instead of B. That's why I try many possible orderings, so hopefully I don't get unlucky like that.
@@jonathanpaulson5053 I thought the same, my solution did not account for this problem in any way (I didn't explore all possible orderings) yet I still got the right answer.
For part 2, I did a recursive search with parameters `curr_node` and `visited` set. Iterate over all adjacent vertices `next_node` such that `next_node > curr_node` and `visited.issubset(adj[next_node])`. This prunes it well enough to run fast, but I'm sure there are inputs where it doesn't.
nice that seems like a better way! I guess it ends up being similar to en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm
Part 2 I made set of all longest lan parties to check - each lan test is always computer+set of connected computers i already had from p1 turns out all initial possible lans are same size - if not you just try largest first trying them is checking for all pairs and just terminating false whenever first doesn't have second in it's set then you create new tests by removing 1 element from every set and adding that to new sets if it doesn't already contain it so just getting tests of lans one smaller for simplicity instead of sets it was dictionary for me, where the key was the sorted string joined answer - since you are just plopping things out, sorting only the initial lan parties would be enough but I was copying sets and they don't guarantee order, anyway the sorts didn't hurt - this way you only check ~7000 sets so it's really not that bad, ~30ms so didn't bother optimizing, but hindsight choosing different collections - and custom type for computer id instead of string maybe would make it better priorityQueue (i guess hashQueue is the name in python) would be good here i guess if the initial lan parties weren't same size, whenever you fail a lan party just push one smaller parties to queue and keep popping by size
@@jonathanpaulson5053yea I googled maximum clique algorithm, found that Wikipedia page, and implemented that haha
I just created a "dict" for sequences and their score per "linenumber". In Go. map[Sequence]map[int]int. Where sequence is just last 4 diffs and add it to map if only there is no present record for the "linenumber" aka first occurance of sequence for initial number. Then just iterate all sequences to find the maximum one for all the line numbers. ~2s in Go
can you please explain what DP is doing here a bit more? I see it's essential to the speed but I'm not 100% on what is it doing
It counts how many ways there are to make a target word/design with the given words/towels. We can solve this recursively by trying all possible first words and then seeing how many ways there are to make the rest of the target. The DP is helpful here if there are many ways to make the same pattern. For example, suppose the words are ["a", "aa"] and the target is "a"*100. There are fibonnaci(100) ways to make the target, but the DP will only evaluate 100 different targets. This is because the DP can share work between different paths - once you've made the first 10 letters of the target, it doesn't matter how exactly you did that, so all ways of making the first 10 letters can share the same work of making the last 90 letters. And it turns out this work-sharing makes a huge difference, because there are many more ways to make each target than they are suffixes of each target.
@@jonathanpaulson5053 thank you, makes sense
This problem took me over a day, and you know its a doozy if even Jon took almost three hours to do it.
Same. Annoying to implement.
I was 100% sure today that part 2 will be about ridiculously huge binary numbers, since all the calculations are bitwise operations. Especially after I made some mistakes in my first implementation of calculcations, and saw some really big numbers. So I spent about an hour and a half implementing my own bit number operations class and had a lot of fun, and it did work! But of course that was really slow, so for part 2 I had to fallback to regular bitwise operations after that anyway :)
Well im 5 days behind, was just checking your times, and seeing 3h scares me a lot
I think I'm doing AoC wrong (first year I'm doing it), because I got a ranking PB solving part 1 by hand today. Definitely not the way, but still "fun". Thanks for sharing how it's meant to be done (at least part1!)
There is no* wrong way of doing AoC, as long as you get the answer ^^ *excluding AI usage
Sounds fun to do by hand! Was it tough to tell if you had the best solution or not?
@@jonathanpaulson5053 I didn't prove it formally, but I basically worked out the cost to move a robot and press the key it lands on in 8 directions (UP, DOWN,LEFT, RIGHT + pairs (UP,RIGHT) ... ), based on the cost of moving the robot above to send the commands. Extra steps outside the 3x3 around the current place are just +1 cost per step in x or y, because you just add 1 extra 'A' press all the way up the chain. E.g. Cost(UP 3) = Cost(UP 1) + 2, Cost (DOWN 10, LEFT 5) = Cost (DOWN 1,LEFT 1) + 13. Then you know the costs to move this robot and can do the next in turn, looking at the keys the current one needs to send, on a round trip e.g. 'A' -> Key 1 -> Key 2 -> 'A' (& press), or 'A' -> Key 2 -> Key 1 -> 'A' , if cheaper (but keep note of the worse ordering of keys - see below). For simple directions the trip is only 'A' -> Key1 -> 'A', so just find the cost of the legal route to Key1 and back. To get to keys involving moves in both axes, you need to be careful, because sometimes the optimal move takes you via a gap, so you need 2 costs for the diagonals (a minimal, and a safe one) then take the safe one if your next key has a path via a gap. E.g: From 'A' to '<', costs a (DOWN,LEFT) +1 for the current bot, but you must take the safe cost (may be higher) because you can't go LEFT then DOWN. If that makes any sense... So, for part 2 I coded up the formula to compute each 3x3 matrices based on its predecessor (each element has a formula that's just a linear combination of elements from the predecessor or a minimum of 2 L.C.s in the diagonals), plus 4 extra values for the "Safe" diagonal costs. Iterate 26 times, and then cost up the moves needed on the final pad.
I'm thrilled to see my solution and yours are pretty identical...except you implemented it about 10x faster!
Okay, I am fucking scared after seeing the length of the video.
i have 4x multiplier of Jonathan, so for me it should be 12h? :D not watched, just commenting...
13 minutes for p1. 2.5 hours for P2. wow
No need for dijkstra, it's far easier to do a DFS (bcs of constant need to press A you can segment out each button1 -> button2 sequence on each depth), and cache all of the results
This guy had his best part 1 performance and the worst part 2 performance on the same day. Anyways, congrats for the 2nd place, i myself got 50s for part 2 too !
Agreed, I think the explanation of a "cheat" could've been better
For part 2, I looped through each position in the path once. At each point, loop through every other point on the path (all cheats must end on the path). If the Manhattan distance is <= 20 for that point and dist_along_path(p2) - dist_along_path(p1) - manhat_dist <= 100, the point is valid. Python3 on my machine takes ~8s, don't know if there's a way to get around needing the second loop to lower the time complexity
almost 3 hours omg ,, congrats for the 2 place
after long long painful solving, trying more approaches, then refactored leveraging statement that there is just 1 "linear" path through, no standard BFS nor DP is needed ;-) and runs ~0.5 sec path is enough and search space is path position combinations - point vs further points within cheat manhatthan dist def solve(text, threshold, cheat_len): G = [[x for x in line.strip()] for line in text.split(" ")] R = len(G) C = len(G[0]) sr, sc = None, None er, ec = None, None for r in range(R): for c in range(C): if G[r][c] == "S": sr, sc = r, c G[r][c] = "." elif G[r][c] == "E": er, ec = r, c G[r][c] = "." seen = set() path = [] r, c, d = sr, sc, 0 while (r, c) != (er, ec): path.append((r, c)) seen.add((r, c)) d += 1 for rr, cc in [(r - 1, c), (r, c + 1), (r + 1, c), (r, c - 1)]: if 0 <= rr < R and 0 <= cc < C and G[rr][cc] == "." and (rr, cc) not in seen: r, c = rr, cc path.append((er, ec)) res = 0 for i in range(0, len(path) - 1): for j in range(i + 1, len(path)): r1, c1 = path[i] r2, c2 = path[j] cheat_diff = abs(r1 - r2) + abs(c1 - c2) if cheat_diff <= cheat_len: norm_diff = j - i if norm_diff - cheat_diff >= threshold: res += 1 return res
12:52 I felt that
bruteforcing p1 with astar and go, finished in dozen seconds, gave up for part 2 :D and bruteforcing cheats
I also found the instructions rather confusing and unclear and have seen other people get confused as well. When reading the problem very carefully, I think everything is explained but it's very light on the details and there are a fair few rather ambiguous statements with no examples that clear them up.
there's only 1 route from S to E, so I dijkstra for a cost map first, and for every next point(deterministic) since and including S, - find cheat points [p1, p2, ...] and steps to these points: exactly 2 steps away for p1, 1-20 steps for p2 - for every point p, (S, p) is a new unique pair, pairs are only valid cheat when it's closer to current point than the normal route, that is: (saving = cost-p - cust-r - step) > 0 - insert this pair into global map of {saved-steps: pairs} it's still slow but not that slow.. implementation is somehow tedius
suppose you have calculated dp[i][j] where it's a min distance from S to (i, j) then if we cheat form (i,j) to (ni, nj), we can simply recalculate it with formula dist = cost - dp[ni][nj)] + dp[i][j] + abs(i - ni) + abs(j - NJ) where cost = d[End_i][End_j] Hence only one bfs is needed
Perfect
Thank you for all your effort.
DP is just magic
❤
After day 11, this day was also memoization. Instead of manually having the ""DP" set, you can just put an @cache decorator before the function (from functools).
Tried that on a later problem but my version of pypy3 is so old it doesn't have it!
I solved part 1 with a depth-first-search. But handling the different combinations for part2 is kind of hard this way. Do you think it would be possible (in a reasonable amount of time) with a depth-first-search?
This kind of recursion is actually a depth-first search
@@Tresorthas i know. but iam struggling to write that in an non recursive way. (With a queue)
@@DerUltraLordMF It's a bit harder. You have to store the way you got to the current query, and increment every parts once you get to a cached ending: DP = {'': 1} def ways(design): to_process = [[design]] while to_process: words = to_process.pop() word = words[-1] if word in DP: for w in words[:-1]: DP[w] += DP[word] else: DP[word] = 0 for pattern in patterns: if word.startswith(pattern): to_process.append(words + [word[len(pattern):]]) return DP[design]
@@Tresorthas Thank you very much! I tried just to store to current substring which has to be processed in the queue
My solution to part 1 was to remove any words that were themselves composite words which sped things up enough to run an exhaustive search which would break after finding a solution, but it was still slow (around 13s) but that also meant part 2 wasn't possible, so I had to do something else and came up with basically the same solution as you.
Re memoization being that fast even for an answer in the hundreds of trillions - my cache info had 23390 hits and a final size of 12634. The number of combinations of letters here just isn't that large, I think edit: ah, you go over this at the end
Nice solution! Get well soon Jonathan. I really enjoy watching your AoC Videos after solving myself to see how dumb my solutions are. :D Keep up the good work!
Thank you for the videos and commentary. I watched them all last year and was looking forward to them again this year. Even though I've switched to C++, seeing your Python solutions is still fun.
also hit the 70+1 error...
I did astar. For p2 I reversed it (started with all corrupt bytes in) and went until it did find a path, but idk if that was maybe only good for my data (my answer was on line 3000 out of 3400 something)