Skip to content

Commit 69bf41e

Browse files
committed
lots of typo and wording fixes in 2019's READMEs
1 parent 80d1fd3 commit 69bf41e

File tree

15 files changed

+25
-25
lines changed

15 files changed

+25
-25
lines changed

Diff for: 2019/03/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ The number of steps a wire takes is the total number of grid squares the wire ha
6969
...........
7070

7171

72-
In the above example, the intersection closest to the central port is reached after `8+5+5+2 = _20_` steps by the first wire and `7+6+4+3 = _20_` steps by the second wire for a total of `20+20 = _40_` steps.
72+
In the above example, the intersection closest to the central port is reached after `8+5+5+2 =` _`20`_ steps by the first wire and `7+6+4+3 =` _`20`_ steps by the second wire for a total of `20+20 =` _`40`_ steps.
7373

74-
However, the top-right intersection is better: the first wire takes only `8+5+2 = _15_` and the second wire takes only `7+6+2 = _15_`, a total of `15+15 = _30_` steps.
74+
However, the top-right intersection is better: the first wire takes only `8+5+2 =` _`15`_ and the second wire takes only `7+6+2 =` _`15`_, a total of `15+15 =` _`30`_ steps.
7575

7676
Here are the best steps for the extra examples from above:
7777

Diff for: 2019/06/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ Your puzzle answer was `484`.
120120

121121
## Solution Notes
122122

123-
Some classic operations on an directed acyclic graph (DAG), a.k.a. trees. The important trick for the second part is to find the "common ancestor" of the `YOU` and `SAN` nodes and then just add the subgraph depths together.
123+
Some classic operations on a directed acyclic graph (DAG), a.k.a. a tree. The important trick for the second part is to find the "common ancestor" of the `YOU` and `SAN` nodes and then just add the subgraph depths together.
124124

125125
* Part 1, Python: 113 bytes, <100 ms
126126
* Part 2, Python: 165 bytes, <100 ms

Diff for: 2019/07/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ Your puzzle answer was `69816958`.
9090

9191
## Solution Notes
9292

93-
One of the puzzles that just need to be coded down without requiring any special ideas. Python's `itertools.permutations` method is key here.
93+
One of the puzzles that just need to be coded down without requiring any special ideas. Python's `itertools.permutations` function is a key asset here.
9494

9595
* Part 1, Python: 462 bytes, ~150 ms
9696
* Part 2, Python: 603 bytes, ~150 ms

Diff for: 2019/12/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ Your puzzle answer was `376243355967784`.
243243

244244
Part 1 is quite fun, but part 2 raises the bar considerably. As the task description implies, there has to be some kind of sub-state periodicity which needs to be detected and extrapolated for the whole system. My initial attempt was to detect periodicity in each of the 24 variables independently, but that turned out to be the wrong approach. Instead, the right thing to do is decompose the system by dimension: Each axis' state is computed independently of the other axes!
245245

246-
For part 2, I have to solutions. The first attempt was to recycle the code from part 1, but it was rather complicated to separate the axes after running the simulation in the way part 1 did (and had to do, because the energy computation *required* cross-axis operations). I tried again and made maximum use of the whole axis separation thing: The simulations are run for each of the three dimensions in turn. This results in a much smaller and even slightly faster implementation.
246+
For part 2, I have two solutions. The first attempt was to recycle the code from part 1, but it was rather complicated to separate the axes after running the simulation in the way part 1 did (and had to do, because the energy computation *required* cross-axis operations). I tried again and made maximum use of the whole axis separation thing: The simulations are run for each of the three dimensions in turn. This results in a much smaller and even slightly faster implementation.
247247

248248
The *real* fun part, though, was writing the interactive (OpenGL-based) visualizer later on :) It's rather surprising how such a simple system (let alone one operating purely on integers) can produce such smooth, yet chaotic, trajectories.
249249

Diff for: 2019/13/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ Your puzzle answer was `15156`.
3939

4040
## Solution Notes
4141

42-
This is a proper simulation of a Breakout game! The hard (or rather: hard to explain) part like ball physics is in the Intcode program, all the user has to do is provide input. Playing the game as a human is nearly impossible; it's too long and requires *very* precise inputs. So, the actual task is to write a ... well, let's say "AI" for it: Track the ball and paddle positions and have the paddle follow the ball all the time.
42+
This is a proper simulation of a Breakout game! The hard (or rather: hard to explain in prose) problems like ball/paddle/brick physics are encoded in the Intcode program, and all the user has to do is provide input. Playing the game as a human is nearly impossible; it's too long and requires *very* precisely timed inputs. So, the actual task is to write a ... well, let's say "AI" for it: Track the ball and paddle positions and have the paddle follow the ball all the time.
4343

4444
For golfing, some shortcuts can be made: Part 1 draws the playfield exactly once, without any overdraw, so counting the drawn block tiles is sufficient. Part 2 doesn't require any kind of display or framebuffer either: The ball and paddle's X positions are simply latched whenever the objects are drawn, and the score is stored if something with a negative X coordinate is output. The Y coordinate isn't used at all.
4545

46-
The golf version expands on this and stores just two dictionaries: The first (`w`) is a map from X coordinates to values; for X >= 0, it doesn't contain anything meaningful (it's the tile ID of the last painted tile in that column), but `w[-1]` contains the current score. The other dictionary (`z`) maps tile IDs to their most recently seen X coordinate, which can then be used to look up the position of the paddle (`z[3]`) and ball (`z[4]`). This dictionary also contains a lot of bogus entries for each score that has been seen in the past (as score updates are just writes into "column -1"); in theory, a score of `3` or `4` could thus disturb the ball/paddle logic. In practice, the score increments per cleared block are around 50 though, so such a situation doesn't occur.
46+
The golf version expands on this and stores just two dictionaries: The first (`w`) is a map from X coordinates to values; for X >= 0, it doesn't contain anything meaningful (it's the tile ID of the last painted tile in that column), but `w[-1]` contains the current score. The other dictionary (`z`) maps tile IDs to their most recently seen X coordinate, which can then be used to look up the position of the paddle (`z[3]`) and ball (`z[4]`). This dictionary also contains a lot of bogus entries for each score that has been seen in the past (as score updates are just writes of arbitrary large values); in theory, a score of `3` or `4` could thus disturb the ball/paddle logic. In practice, the score increments per cleared block are around 50 though, so such a situation doesn't occur.
4747

4848
The non-golf implementation contains a console visualization:
4949

Diff for: 2019/16/README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ Part 2, however, is where the fun ends. Simply computing the result as usual is
102102

103103
<details><summary>There are two thing to note. (Spoilers!)</summary>
104104

105-
First, the bottom half of the transform matrix is an upper diagonal matrix (and a very trivial one at that). For example, in an 8-element "FFT" transform:
105+
First, the transform matrix is an upper diagonal matrix, and its bottom half is particularily simple. For example, in an 8-element "FFT" transform:
106106

107107
1 0 complicated
108108
0 1 1 0 0 stuff
@@ -113,13 +113,13 @@ First, the bottom half of the transform matrix is an upper diagonal matrix (and
113113
0 0 0 0 0 0 1 1
114114
0 0 0 0 0 0 0 1
115115

116-
As a result, the second half of the input signal can be trivially computed by summing elements together, starting at the end. In particular, the final digit of the vector never changes, as the last row is always `0 ... 0 1`.
116+
As a result, the second half of the input signal can be trivially computed by summing elements together, starting at the end. In particular, the final digit of the vector never changes.
117117

118118
Second, the first seven digits of the input are constructed such that they point somewhere towards the *end* of the 6.5M-element result vector (in my case, at ~6M).
119119

120120
<details><summary>The solution can be found by combining these facts. (More Spoilers!)</summary>
121121

122-
If only the result of some position in the last half of the input is desired, *all elements before that can be ignored and do not need to be computed*. This is also true when performing the transform multiple times, so for all intents and purposes, we can just throw away the initial ~90% of the vector at the very beginning. And for the remaining ~530k elements, the computation of the transform is rather simple, as described above.
122+
If only the result of some position in the last half of the input is desired, *all elements before that can be ignored and do not need to be computed*. This is also true when performing the transform multiple times, so for all intents and purposes, we can just throw away the initial ~90% of the vector before computing anything. And for the remaining ~530k elements, the computation of the transform is rather simple, as described above.
123123

124124
</details></details>
125125

Diff for: 2019/17/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,9 @@ Part 2 is where the meat is. Fortunately, the inputs are constructed in a way th
142142

143143
2. Parse the map, extract the locations of the scaffold path, and the robot's starting location and heading. As is turns out, the initial heading is always north (`^`), a fact that I happily made use of in the golf implementation.
144144

145-
3. Generate the raw instructions for traversing the scaffold. It's always a single path that doesn't bifurcate, and the starting position is at one end of the path, so there's no need to backtrack at any point. The sequence thus contains alternating turn and run commands, which can be grouped together into "packets" like `L,4` or `R,10`. Furthermore, the scaffold where the robot starts is always oriented in east-west direction, even though the robot starts facing north, so the sequence always starts with a full packet containing an `L` or `R` too.
145+
3. Generate the raw instructions for traversing the scaffold. It's always a single path that doesn't bifurcate, and the starting position is at one end of the path, so there's no need to backtrack at any point. The sequence thus contains alternating turn and run commands, which can be grouped together into "packets" like `L,4` or `R,10`. Furthermore, the scaffold where the robot starts is always oriented in east-west direction, even though the robot starts facing north, so the sequence always starts with a full packet containing an `L` or `R`.
146146

147-
4. Split the sequence into a pattern that references up to three subsequences, obeying the 20-character length limits. This is pretty much what a Lempel-Ziv-based data compressor would do, except that the constraints force a so-called "optimal parse"; simple greedy matching will not suffice. I used a DFS for that, but there may be other approaches. The good news is that the puzzle input is constructed such that it's never necessary to split up runs, i.e. just working on the "packets" generated in step 3 is sufficient.
147+
4. Split the sequence into a pattern that references up to three subsequences, obeying the 20-character length limits. This is pretty much what a Lempel-Ziv-based data compressor would do, except that the constraints force a so-called "optimal parse"; simple greedy matching will not suffice. I used a DFS for that, but there may be other approaches. The good news is that the puzzle input is constructed such that it's never necessary to split runs, i.e. just working on the "packets" generated in step 3 is sufficient.
148148

149149
5. Compile the final commands, send them into the program and receive the final map and the result value.
150150

Diff for: 2019/18/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ The first step is what took longest to get right. A straight BFS is not sufficie
282282

283283
The second step is classical DFS, but with a naive implementation, the runtime for more than approx. 10 keys is far too long. Caching already explored states helped to get the runtime down comfortably into the sub-second zone.
284284

285-
Based on that framework, part 2 of the puzzle isn't a large problem. Step 1 doesn't change at all, and step 2 simply needs to track four current locations instead of just one.
285+
Based on that framework, part 2 of the puzzle isn't much of a problem. Step 1 doesn't change at all, and step 2 simply needs to track four current locations instead of just one.
286286

287287
* Part 1, Python: 666 bytes, ~300 ms
288288
* Part 2, Python: 842 bytes, ~700 ms

Diff for: 2019/19/README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,13 @@ Your puzzle answer was `9441282`.
8686

8787
## Solution Notes
8888

89-
Part 1 is well solvable with brute-force iteration, though the runtime is not optimal. The surprising part is that the Intcode VM needs a full restart (including re-initializing the memory) between each query; I assumed an endless loop that repeatedly queries coordinates instead, which cost me a few minutes of debugging.
89+
Part 1 is well solvable with brute-force iteration, though the runtime is not optimal. The surprising part is that the Intcode VM needs a full restart (including re-initializing the memory) between each query; I assumed that it runs an endless loop that repeatedly queries coordinates instead, which cost me (and many other contestants) a few minutes of debugging.
9090

9191
For part 2, it's clear that brute force is no longer an option. Instead, the edges of the beam need to be traced somehow; I chose the upper edge, and try to find the lowest position for which both (X,Y) and (X-99,Y+99) are in the beam.
9292

93-
My initial approach (expecting the coordinates to be very large) was to compute the slope of the edge, so I have a good approximation of where the upper edge's Y is for any X. Then I did a coarse search for X in high increments, followed by a fine search. This works and is really fast, but it has two caveats: First, the code is quite large. Second, there are situations where a 100x100 square can be found for X-1, but not for X, i.e. the "can a square be found" function is not monotonically increasing with X. This is bad if the coarse search lands at such a local minimum; that actually happened to me until I tweaked the search parameters accordingly.
93+
My initial approach (expecting the coordinates to be very large) was to compute the slope of the edge, so I have a good approximation of where the upper edge's Y is for any X. Then I did a coarse search for a working X with a large step size, followed by a fine search. This works and is really fast, but it has two caveats: First, the code is quite large. Second, there are situations where a 100x100 square can be found for X-1, but not for X, i.e. the "can a square be found" function is not monotonically increasing with X. This is bad if the coarse search lands at such a local minimum; that actually happened to me until I tweaked the search parameters accordingly.
9494

95-
Since the coordinates aren't *sooo* high to begin with, I tried again with a much simpler approach: Just walk along the upper edge until the square fits. This is naturally much slower, but still bearable, and it's 42 bytes smaller, so I'm fine with that.
95+
Since the coordinates turned out to be not *that* large, I tried again with a much simpler approach: Just walk along the upper edge until the square fits. This is naturally much slower, but still bearable, and it's 42 bytes smaller, so I'm fine with that.
9696

9797
* Part 1, Python (brute force): 494 bytes, ~3.5 s
9898
* Part 2, Python (fast search with slope estimation): 561 bytes, ~350 ms

Diff for: 2019/20/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ Your puzzle answer was `6208`.
216216

217217
Part 1 is just a simple BFS; the most challenging part is parsing the input.
218218

219-
Part 2 extends this to a third dimension, but is still manageable complexity-wise (even though I can no longer use my beloved complex numbers as coordinate representation). The problem there is that the complexity is so high that naive BFS blows apart. Performing two independent BFSs from the start and goal and stopping when they meet was the key to success there.
219+
Part 2 extends this to a third dimension, but is still manageable in terms of code complexity (even though I can no longer use my beloved complex numbers as coordinate representation). The problem there is that the runtime complexity is so high that naive BFS blows apart. Performing two independent BFSes from start and goal and stopping when they meet was the key to success here.
220220

221221
* Part 1, Python: 432 bytes, <100 ms
222222
* Part 2, Python: 667 bytes, ~600 ms

Diff for: 2019/22/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ Your puzzle answer was `3920265924568`.
176176

177177
Part 1 is nice and easy. After doing a simple full-deck simulation, I noticed that it's much easier to just track the position of card 2019.
178178

179-
Part 2 is where all that nasty modular arithmetic math comes into play. The key ingredient is to recognize that each shuffle operation corresponds to a simple `(a*x + b) mod N` transforma: "cut" is addition, "deal with increment X" is multiplication by the [multiplicative inverse](https://en.wikipedia.org/wiki/Modular_multiplicative_inverse) of X modulo N &ndash; and "deal into new stack", it turns out, is basically `deal with increment N-1` followed by `cut 1`. Using this, it's possible to collapse the whole shuffle instruction list into a single `a,b` value pair. The effect of applying the whole transformation `p` times is then `(a*x + b)^p mod N`. It took me an eternity to figure this out, but this can be computed by an extended implementation of [modular exponentiation](https://en.wikipedia.org/wiki/Modular_exponentiation), which takes O(log N) time. The result is a new `a,b` pair that can be applied to `x=2020` to get the final result.
179+
Part 2 is where all that nasty modular arithmetic math comes into play. The key ingredient is to recognize that each shuffle operation corresponds to a simple `(a*x + b) mod N` transform: "cut" is addition, "deal with increment X" is multiplication by the [multiplicative inverse](https://en.wikipedia.org/wiki/Modular_multiplicative_inverse) of X modulo N &ndash; and "deal into new stack", it turns out, is basically `deal with increment N-1` followed by `cut 1`. Using this, it's possible to collapse the whole shuffle instruction list into a single `a,b` value pair. The effect of applying the whole transformation `p` times is then `(a*x + b)^p mod N`. It took me an eternity to figure out, but this can be computed by an extended implementation of [modular exponentiation](https://en.wikipedia.org/wiki/Modular_exponentiation), which takes O(log N) time. The result is a new `a,b` pair that can be applied to `x=2020` to get the final result.
180180

181181
My first implementation of part 2 builds the decomposition in reverse order, after a lot of trial-and-error iterations. Later, I found out that it's just as easy to generate the decomposition in forward order (in fact, that's what I tried to do first, but failed, and settled with reverse). This saves a few more bytes.
182182

Diff for: 2019/23/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Your puzzle answer was `11046`.
3939

4040
A nice and simple puzzle with no unpleasant surprises.
4141

42-
For golfing, I first did the obvious thing and turned the 50 machines into class instances. For the second revision, I put all the machine state (`IP` and `RB` registers, input and output queue) into the main memory dictionary at addresses `-1` to `-4`, which is safe because negative addresses are otherwise forbidden by the Intcode spec. This makes register and queue access slighly longer (`X.p` becomes `X[-1]`, +1 byte), but this is far outweighed by the shorter memory accesses (`X.m[i]` -> `X[i]`, -2 bytes) and instantiation.
42+
For golfing, I first did the obvious thing and turned the 50 machines into class instances. For the second revision, I put all the machine state (IP and RB registers, input and output queue) into the main memory dictionary at addresses `-1` to `-4`, which is safe because negative addresses are otherwise forbidden by the Intcode spec. This makes register and queue access slighly longer (`X.p` becomes `X[-1]`, +1 byte), but this is far outweighed by the shorter memory accesses (`X.m[i]` -> `X[i]`, -2 bytes) and instantiation.
4343

4444
* Part 1, Python (machines as classes): 690 bytes, ~150 ms
4545
* Part 1, Python (machines as dictionaries): 668 bytes, ~150 ms

0 commit comments

Comments
 (0)