Skip to content

Commit 5fe810e

Browse files
committed
Day 12 docs, before refactoring
1 parent d268935 commit 5fe810e

File tree

3 files changed

+103
-1
lines changed

3 files changed

+103
-1
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ _Warning:_ I write long explanations. So... yeah.
2121
| 9 | [source](src/advent_2020_clojure/day09.clj) | [blog](docs/day09.md) |
2222
| 10 | [source](src/advent_2020_clojure/day10.clj) | [blog](docs/day10.md) |
2323
| 11 | [source](src/advent_2020_clojure/day11.clj) | [blog](docs/day11.md) |
24+
| 12 | [source](src/advent_2020_clojure/day12.clj) | [blog](docs/day12.md) |

docs/day12.md

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Day Twelve: Rain Risk
2+
3+
* [Problem statement](https://adventofcode.com/2020/day/12)
4+
* [Solution code](https://github.com/abyala/advent-2020-clojure/blob/master/src/advent_2020_clojure/day12.clj)
5+
6+
---
7+
8+
I liked today's puzzle, because it became clear after implementing Part 1 that there's a logical
9+
rewrite to do to make both parts look the same. This time around, I'm going to provide my solution
10+
to part 1 without knowing anything about part 2, and then we'll do a refactoring.
11+
12+
---
13+
14+
# Part 1
15+
16+
In this part, we are given a list of instructions and are given instructions on how to steer the
17+
ship. Each instruction has a single-character operation, following by a numeric argument.
18+
We move north, south, east, or west _without changing direction_ if the operation is `N`, `S`, `E`,
19+
or `W`. We turn the ship without moving it counter-clockwise or clockwise, `L` or `R`, by the number
20+
of degrees; note that we only get multiples of 90 to keep the math simple. And finally, we can move
21+
the ship forward, `F`, in the direction it's currently facing.
22+
23+
The first part I want to solve is how to rotate the ship with the `L` and `R` instructions. I
24+
define a vector of all possible directions going in a clockwise motion. Then in the `rotate`
25+
function, the goal is to find the index of the current direction, and add `(quot amt 90)` to the
26+
index, since each 90 degrees will move us to the next direction in the circle. We use
27+
`(mod (count dirs))` to wrap around the edges, and then we grab the direction at the new index.
28+
29+
Note that there's another way to write this function. After finding the index and the number of steps
30+
to move, instead of using `mod`, we can just find the `nth` location in an infinite cycle of the
31+
original direction list. It's less efficient if for some reason we had to rotate a 9 million degrees,
32+
but it's a neat option.
33+
34+
```clojure
35+
(def dirs [:north :east :south :west])
36+
(defn rotate [dir amt]
37+
(-> (.indexOf dirs dir)
38+
(+ (quot amt 90))
39+
(mod (count dirs))
40+
dirs))
41+
42+
; inefficient alternative
43+
(defn rotate [dir amt]
44+
(->> (.indexOf dirs dir)
45+
(+ (quot amt 90))
46+
(nth (cycle dirs))))
47+
```
48+
49+
Let's take a quick moment to think about the state we're going to use. A ship is represented by two
50+
pieces of data - the current `[x y]` coordinates and the direction we're facing. I could use a map
51+
of these values, or a 3-element vector, but a two element vector of the `[x y]` pair and the direction
52+
seemed simple enough. So the starting position of facing east will be `[[0 0] :east]`.
53+
54+
Now, let's figure out how to move the ship without rotating it. We'll write a function called `move`,
55+
which takes in the current position, direction to move, and the amount to move. Just as we made a
56+
vector of all directions, we'll make a map of each direction to the vector we'll add to move once in
57+
that direction. Most AoC functions assume that the origin is in the top-left corner of the input and,
58+
`x` moves to the right while `y` moves _down_. It's strange but I've become used to it, but it does
59+
mean that north is `[0 -1]` instead of a more traditional `[0 1]`.
60+
61+
To move, we find the amount to move from the `dir-amounts` map, multiply the `[dx dy]` pair by the
62+
`amt` value to know how far we need to move in total, and then use `(mapv + [x y]`) to add it to the
63+
current position of the ship.
64+
65+
```clojure
66+
(def dir-amounts {:north [0 -1]
67+
:south [0 1]
68+
:east [1 0]
69+
:west [-1 0]})
70+
71+
(defn move [[[x y] dir] amt]
72+
(->> (dir-amounts dir)
73+
(mapv * [amt amt])
74+
(mapv + [x y])))
75+
```
76+
77+
Then we create the `next-state` function to move the state based on the current line in the instructions.
78+
We'll destructure the heck ouf of the input again, reading in `state` as `[[x y] dir :as state]` to pull
79+
apart the values and keep the state intact at the same time. Then we parse the `op` and `amt` out of the
80+
instruction, and use `case` to switch based on the `op`. For each value, I just manually reconstruct the
81+
vector by manually changing the point. It's not clean, but again we're about to rewrite this.
82+
83+
```clojure
84+
(defn next-state [[[x y] dir :as state] line]
85+
(let [op (first line)
86+
amt (-> (subs line 1) Integer/parseInt)]
87+
(case op
88+
\N [[x (- y amt)] dir]
89+
\S [[x (+ y amt)] dir]
90+
\E [[(+ x amt) y] dir]
91+
\W [[(- x amt) y] dir]
92+
\L [[x y] (rotate dir (- amt))]
93+
\R [[x y] (rotate dir amt)]
94+
\F [(move state amt) dir])))
95+
```
96+
97+
Finally, the `part1` function uses the `reduce` function on the starting state, calling `next-state` to
98+
process each line. Then when we're done, we grab the `[x y]` coordinates out of the state using `first`,
99+
and calculate the Manhattan distance by taking the absolute value of `x` and `y` and adding them.
100+

src/advent_2020_clojure/day12.clj

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414
:south [0 1]
1515
:east [1 0]
1616
:west [-1 0]})
17+
1718
(defn move [[[x y] dir] amt]
1819
(->> (dir-amounts dir)
19-
(mapv * (repeat amt))
20+
(mapv * [amt amt])
2021
(mapv + [x y])))
2122

2223
(defn next-state [[[x y] dir :as state] line]

0 commit comments

Comments
 (0)