Skip to content

Commit b2f64ce

Browse files
committed
runtime and stats update
The runtime statistics for 2024 are now measured on the same machine as was used for 2015-2023 (Core i5-6200U, 2.3 GHz mobile Intel Skylake), to allow for direct comparisons. Originally, the statistics have been generated on a mix of several, faster, machines.
1 parent 5febf79 commit b2f64ce

18 files changed

+257
-203
lines changed

2024/04/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,5 +79,5 @@ Your puzzle answer was `1982`.
7979

8080
A very simple task, and part 2 is actually even simpler than part 1. The nice thing is that there are again many ways to implement it: A 2D array of characters is fine, but so is a flat coordinate-to-character mapping. I ended up with my usual complex numbers as coordinate representation.
8181

82-
* Part 1, Python: 180 bytes, ~250 ms
82+
* Part 1, Python: 180 bytes, ~300 ms
8383
* Part 2, Python: 172 bytes, <100 ms

2024/05/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,6 @@ The actually recommended way of doing things is as blunt as it gets: For each up
105105

106106
The slightly annoying aspect of this puzzle is indeed input parsing, at least as far as code golf is concerned. The approach that works best for part 1 (in the sense of "least amount of code") isn't really suitable for part 2, so I ended up with completely different ways of handling input between the parts.
107107

108-
* Part 1, Python: 199 bytes, <100 ms
109-
* Part 2, Python: 283 bytes, ~150 ms
110-
* Parts 1+2, Python: 299 bytes, ~150 ms
108+
* Part 1, Python: 199 bytes, ~150 ms
109+
* Part 2, Python: 283 bytes, ~300 ms
110+
* Parts 1+2, Python: 299 bytes, ~300 ms

2024/06/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,5 +202,5 @@ Part 1 is a fun exercise in utilizing the good old playfield-as-dictionary-index
202202
For part 2, there are two challenges: First, getting the loop check conditions exactly right, which is a bit finicky; and second, reducing the search space so runtime is acceptable. My very first approach tried to put an obstacle at every point of the maze (which has 130x130 elements in my case), but that took more than half a minute to compute. A straightforward way to reduce the search space is to only put obstacles where the guard's normal path (from part 1) was. This is only good for a ~3x reduction, but it's better than nothing, and allows for solving both parts with little more code than what's needed part 2 alone.
203203

204204
* Part 1, Python: 193 bytes, <100 ms
205-
* Part 2, Python: 318 bytes, ~10 s
206-
* Parts 1+2, Python: 350 bytes, ~10 s
205+
* Part 2, Python: 318 bytes, ~25 s
206+
* Parts 1+2, Python: 350 bytes, ~25 s

2024/07/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,6 @@ This boils down to a DFS-like recursive algorithm that receives the current valu
6060

6161
To little surprise, part 2 takes substantially longer to compute than part 1. Not only is 3<sup>n</sup> much more than 2<sup>n</sup>; the integer-to-string-and-back roundtrip used for the concatenation operation also isn't cheap. There are plenty of ways to optimize things though: First, there's no need to try further operations when a result has been found; second, since all operations only cause the numbers to grow, recursion can stop if the current value is already larger than the test value; and third, the string conversion roundtrip can be replaced with a multiplication by a power of 10 based on the value of the second operand. I implemented the first two strategies in my code and also got rid of my golf-induced abuse of `*args` to get a decent ~4x speedup; the last strategy is not at all conductive to code golf, so I stopped at that point.
6262

63-
* Part 1, Python: 148 bytes, ~150 ms
64-
* Part 2, Python (naive): 182 bytes, ~7 s
65-
* Part 2, Python (optimized): 209 bytes, ~2 s
63+
* Part 1, Python: 148 bytes, ~250 ms
64+
* Part 2, Python (naive): 182 bytes, ~15 s
65+
* Part 2, Python (optimized): 209 bytes, ~3.5 s

2024/09/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,5 @@ Your puzzle answer was `6421724645083`.
8282
This puzzle is mostly interesting in the sense that the two parts call for completely different data structures to get the most effective solution. For part 1, a simple map of each block on the disk, just like the ASCII printouts in the puzzle description, does the job perfectly. For part 2, two separate lists of extents for the files and the empty spaces are basically a requirement. Even with that, the solution can be quite slow, as the empty space list needs to be searched a lot. A substantially faster, albeit larger (but still somewhat golfable) solution is to further segregate the empty space list into bins for each block size. When looking for a place to move a file into, only the first positions for each of the 10 empty space bins need to be examined, instead of several thousands from a raw list. However, the remaining free space after a move operation must be inserted at the proper place in the target bin's list so that it stays sorted; this is where Python's `heapq` module shows that it _does_ in fact have a use outside of Dijkstra's algorithm ;)
8383

8484
* Part 1, Python: 195 bytes, <100 ms
85-
* Part 2, Python (minimum size): 246 bytes, ~6 s
85+
* Part 2, Python (minimum size): 246 bytes, ~15 s
8686
* Part 2, Python (speed-optimized): 335 bytes, <100 ms

2024/11/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,6 @@ The other way, which turned out to be even more compact, is based on the observa
7979

8080
By the way, it turned out to be consistently a ~~bit~~ byte smaller to keep the numbers as strings and only convert them to integers if needed.
8181

82-
* Part 1, Python: 179 bytes, ~250 ms
83-
* Part 2, Python (type-and-frequency): 239 bytes, ~150 ms
84-
* Part 2, Python (DFS + memoization): 230 bytes, ~150 ms
82+
* Part 1, Python: 179 bytes, ~350 ms
83+
* Part 2, Python (type-and-frequency): 239 bytes, ~200 ms
84+
* Part 2, Python (DFS + memoization): 230 bytes, ~200 ms

2024/12/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,5 +154,5 @@ Isolating the several regions obivously calls for connected component analysis;
154154

155155
Part 2 is making things even more complicated, but the solution for that isn't too far-fetched as well: We only need to count _distinct_ runs of neighbors for each direction &ndash; so we can basically run another connected component analysis for the neighbor tiles in each direction.
156156

157-
* Part 1, Python: 268 bytes, ~300 ms
158-
* Part 2, Python: 325 bytes, ~250 ms
157+
* Part 1, Python: 268 bytes, ~500 ms
158+
* Part 2, Python: 325 bytes, ~300 ms

2024/13/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,5 +86,5 @@ There are a few red herrings in the task description: The maximum of 100 for A a
8686

8787
Part 1 is still solvable by brute-forcing through all 10k A/B combinations within the search range, part 2 obviously isn't. Instead, you really need to solve the linear system, and fail if any of the divisions in that process yield a remainder. That's obviously the much faster method, and it completes the task in no time at all.
8888

89-
* Part 1, Python: 182 bytes, ~700 ms
89+
* Part 1, Python: 182 bytes, ~1 s
9090
* Part 2, Python: 210 bytes, <100 ms

2024/14/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ Part 2, then, hits like a truck. There's no specification whatsoever as to what
146146
**Heuristic 4: Independent Axis Minimum Variance** takes the previous approach one step further by throwing even more maths at it. It's based on the observation that the X and Y axes are independent from each other (like in [another (in)famous past puzzle](../../2019/12)), and that the X coordinates of all robots repeat every 101 steps, while the Y coordinates repeat every 103 steps. This also means that the minimum variance states of these axes occur periodically at x0 + nx * 101 and y0 + ny * 103; the time where both coincide is the puzzle answer. So it's sufficient to find the X variance minimum for the first 101 steps and the Y variance minimum for the first 103 steps, and from there on it's all about modular arithmetics &ndash; or maybe not, because the factors are small enough that we can just try X minimum-variance step times until we find one that's also a valid Y minimum-variance step time. The end result is the largest implementation, but also the fastest one by quite some margin.
147147

148148
* Part 1, Python: 198 bytes, <100 ms
149-
* Part 2, Python (Unique Positions): 178 bytes, ~500 ms
150-
* Part 2, Python (Run of Adjacent Spots): 222 bytes, ~6 s
151-
* Part 2, Python (Minimum Variance): 235 bytes, ~1.5 s
149+
* Part 2, Python (Unique Positions): 178 bytes, ~1.5 s
150+
* Part 2, Python (Run of Adjacent Spots): 222 bytes, ~15 s
151+
* Part 2, Python (Minimum Variance): 235 bytes, ~2.5 s
152152
* Part 2, Python (Independent Axis Minimum Variance): 265 bytes, <100 ms

2024/15/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -427,5 +427,5 @@ At this point, I was expecting part 2 to be something like "the instructions are
427427

428428
During initial implementation, I was a bit too scared of the BFS part and added special-case code for left and right movements, as they could use something similar to the old run-of-boxes approach. That was exactly the wrong call &ndash; I lost a lot of time debugging this special-case horizontal movement code, and once the vertical movement code worked, I could simply remove all of it, because as it turns out, the vertical movement code _was_ already generic enough to handle horizontal movement as well ...
429429

430-
* Part 1, Python: 335 bytes, <100 ms
431-
* Part 2, Python: 436 bytes, ~150 ms
430+
* Part 1, Python: 335 bytes, ~150 ms
431+
* Part 2, Python: 436 bytes, ~600 ms

2024/16/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,6 @@ This is a nice twist for an otherwise unassuming pathfinding puzzle, but at the
147147

148148
The only nice thing here is that it's trivial to write a combined solution: Since part 1's result is a byproduct of computing part 2, it's more or less only a matter of an additional `print()` to output both.
149149

150-
* Part 1, Python: 276 bytes, ~1.5 s
151-
* Part 2, Python: 347 bytes, ~2.5 s
152-
* Parts 1+2, Python: 359 bytes, ~2.5 s
150+
* Part 1, Python: 276 bytes, ~2 s
151+
* Part 2, Python: 347 bytes, ~3.5 s
152+
* Parts 1+2, Python: 359 bytes, ~3.5 s

2024/18/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,5 @@ Your puzzle answer was `27,60`.
103103
This is a plain standard BFS maze search. Part 2 can be solved with brute-force iteration in an acceptable time, but the ideal (albeit larger) solution is of course doing a binary search for the first item in the sequence where the BFS can't reach the exit any longer.
104104

105105
* Part 1, Python: 251 bytes, <100 ms
106-
* Part 2, Python (brute force): 262 bytes, ~7 s
106+
* Part 2, Python (brute force): 262 bytes, ~10 s
107107
* Part 2, Python (binary search): 317 bytes, <100 ms

2024/19/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,4 @@ For part 2, the task of the regular expression matcher needs to be spelled out "
9393
Fun fact: Part 2's solution can be turned into part 1 simply by replacing the `abs()` call with `any()`.
9494

9595
* Part 1, Python: 125 bytes, <100 ms
96-
* Part 2, Python: 206 bytes, ~500 ms
96+
* Part 2, Python: 206 bytes, ~700 ms

2024/20/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,5 +199,5 @@ Thinking about it a bit further, I had a much simpler idea: Since the cheat can
199199

200200
The results can be found by checking all possible cheats for all positions `P` in the maze and checking if `d >= 100` for them. The possible cheats for part 1 simply put `Q` two positions left, right, above and below `P` for part 1, or form a diamond-shaped area for part 2.
201201

202-
* Part 1, Python: 283 bytes, ~5 s
203-
* Part 2, Python: 362 bytes, ~6 s
202+
* Part 1, Python: 283 bytes, ~6 s
203+
* Part 2, Python: 362 bytes, ~10 s

2024/22/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,6 @@ Part 2 boils down to observing the previous 4 values modulo 10 and their deltas
134134

135135
Due to all the dictionary manipulation involved, part 2 is no longer significantly faster on PyPy than on CPython. But how fast could it be? Out of curiosity, I implemented a C version of this puzzle, using a flat zero-initialized list instead of hash maps for the central data structure. After all, there are just 19^4 = ~130k possible sequences of four deltas, and not even all of these are valid (9,9,9,9, for example, isn't), so the entire thing fits comfortably into a modern CPU's cache. There only need to be two data items per entry: The resulting score, and some kind of flag to note whether this delta sequence has already been encountered for the current line. Since I didn't want to waste any time on clearing this flag, I opted for a line counter instead: An entry is only updated if it hasn't been updated already for the current input line. The solution is even relatively nicely golfable, coming in at well below 500 bytes, and it executes practically instantaneously, even if the compile time is included.
136136

137-
* Part 1, Python: 120 bytes, ~2 s
138-
* Part 2, Python: 238 bytes, ~7 s
139-
* Parts 1+2, C: 451 bytes, <100 ms
137+
* Part 1, Python: 120 bytes, ~2.5 s
138+
* Part 2, Python: 238 bytes, ~10 s
139+
* Parts 1+2, C: 451 bytes, ~150 ms

2024/25/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,4 @@ You nod, and The Historians quickly work to collect their notes into the final s
130130

131131
A simple implement-as-described task, in theory. In practice, all that quibble about locks vs. keys and pin heights is just a red herring, and the actual task is simply to check all pairs of patterns for collisions at any of the `#` spots.
132132

133-
* Part 1, Python: 165 bytes, ~100 ms
133+
* Part 1, Python: 165 bytes, ~250 ms

0 commit comments

Comments
 (0)