-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy path03_spiral.rmd
314 lines (246 loc) · 10.4 KB
/
03_spiral.rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# Spiral Memory
Compared to [Day 3](https://adventofcode.com/2017/day/3), Days 1 and 2 were a breeze! It definitely put that growing coding ego in check.
## Part I
```
You come across an experimental new kind of memory stored on an infinite two-dimensional
grid.
Each square on the grid is allocated in a spiral pattern starting at a location marked
1 and then counting up while spiraling outward. For example, the first few squares are
allocated like this:
17 16 15 14 13
18 5 4 3 12
19 6 1 2 11
20 7 8 9 10
21 22 23---> ...
While this is very space-efficient (no squares are skipped), requested data must be
carried back to square 1 (the location of the only access port for this memory system)
by programs that can only move up, down, left, or right. They always take the shortest
path: the Manhattan Distance between the location of the data and square 1.
For example:
Data from square 1 is carried 0 steps, since it's at the access port.
Data from square 12 is carried 3 steps, such as: down, left, left.
Data from square 23 is carried only 2 steps: up twice.
Data from square 1024 must be carried 31 steps.
How many steps are required to carry the data from the square identified in your puzzle
input all the way to the access port?
```
After chasing down a few dead ends, I came up with this approach:
Since we care primarily about position, we can re-write our spiral from above as steps from the center (zero):
```
6 5 4 3 4 5 6
5 4 3 2 3 4 5
4 3 2 1 2 3 4
3 2 1 0 1 2 3
4 3 2 1 2 3 4
5 4 3 2 3 4 5
6 5 4 3 4 -->
```
We can think of these numbers as a series of successive square-shaped frames around 0. If we write it out for each frame, then we get:
```
Frame 1: 0
Frame 2: 1 2 1 2 1 2 1 2
Frame 3: 3 2 3 4 3 2 3 4 3 2 3 4 3 2 3 4
Frame 4: 5 4 3 4 5 6 5 4 3 4 5 6 5 4 3 4 5 6 5 4 3 4 5 6
Frame n: .....
```
From this, we see that:
- Each frame (after frame 1) ends with an even number, specifically, $2(n - 1)$
- Each frame (after 1) starts with an odd number, specifically, one less than the even number: $2(n - 1) - 1$ = $2n - 3$
- The minimum number of steps in each frame is equal to $n - 1$
- The maximum number of steps is equal to the last number in the frame, or $2(n - 1)$
- Except for frame 1, the number of digits in each frame is divisible by 4 (since there are four sides in a frame)
```
Frame 1: 0
Frame 2: (1 2) (1 2) (1 2) (1 2)
Frame 3: (3 2 3 4) (3 2 3 4) (3 2 3 4) (3 2 3 4)
Frame 4: (5 4 3 4 5 6) (5 4 3 4 5 6) (5 4 3 4 5 6) (5 4 3 4 5 6)
```
Now, we can start pulling some of these patterns together:
```{r warning = FALSE, message = FALSE}
library(tidyverse)
steps <- tibble(frame = 1:10) %>%
mutate(max_steps = 2*(frame - 1),
min_steps = frame - 1,
start_steps = max_steps - 1,
end_steps = max_steps)
print(steps)
```
Except for the first frames, we know that for each side, the number of steps starts at some number (`start_steps`), decreases by one until it gets to the minimum number of steps (`min_steps`), and then increases by one until it gets to the end, or maximum, number of steps (`end_steps` or `max_steps`). Repeat this four times and we have the whole sequence of steps for each frame.
```{r warning = FALSE, message = FALSE}
positions <- steps %>%
rowwise() %>%
mutate(seq = paste(c(start_steps:min_steps, (min_steps + 1):end_steps),
collapse = " "),
seq4 = paste(rep(seq, 4), collapse = " ")) %>%
mutate(seq4 = replace(seq4, frame == 1, 0)) %>% #replace square 1's sequence with 0
select(seq4)
print(positions)
```
If we now paste it all together as a long string (with a tiny bit of base R), and then separate out again into a vector of integers, we can answer the puzzle question!
```{r warning = FALSE, message = FALSE}
str_c(positions$seq4, collapse = " ") %>%
str_split(" ", simplify = TRUE) %>%
as.numeric()
```
We can't quite get to the puzzle answer with just the first 10 frames, so we'll have to expand our scope a little. Let's pull everything together in one giant pipe:
```{r warning = FALSE, message = FALSE}
positions500 <- tibble(frame = 1:500) %>%
mutate(max_steps = 2*(frame - 1),
min_steps = frame - 1,
start_steps = max_steps - 1,
end_steps = max_steps) %>%
rowwise() %>%
mutate(seq = paste(c(start_steps:min_steps, (min_steps + 1):end_steps),
collapse = " "),
seq4 = paste(rep(seq, 4), collapse = " ")) %>%
mutate(seq4 = replace(seq4, frame == 1, 0)) %>% #replace frame 1's sequence with 0
select(seq4) %>%
str_c(collapse = " ") %>% #see NOTE
str_extract_all("\\d+", simplify = TRUE) %>%
as.numeric()
```
*Note:* Directly using `str_c` on the tibble, rather than subsetting the column using something like `tibble$seq4` results in the inclusion of some special characters. So, rather than `str_split` at all spaces (" "), we can extract all digits instead (using this handy [basic regular expressions in R cheatsheet](https://www.rstudio.com/wp-content/uploads/2016/09/RegExCheatsheet.pdf)).
*Note 2:* After writing this, I discovered the `separate_rows` function which does the same thing much more elegantly (see part II for use).
Let's run some tests to make sure it works!
```{r warning = FALSE, message = FALSE}
positions500[12] == 3; positions500[23] == 2; positions500[1024] == 31
```
:satisfied: :star2: :fireworks:
## Part II
For this section, they make things a bit more complicated...it's no longer only about position.
```
As a stress test on the system, the programs here clear the grid and then store the value 1 in square 1. Then, in the same allocation order as shown above, they store the sum of the values in all adjacent squares, including diagonals.
So, the first few squares' values are chosen as follows:
Square 1 starts with the value 1.
Square 2 has only one adjacent filled square (with value 1), so it also stores 1.
Square 3 has both of the above squares as neighbors and stores the sum of their values, 2.
Square 4 has all three of the aforementioned squares as neighbors and stores the sum of their values, 4.
Square 5 only has the first and fourth squares as neighbors, so it gets the value 5.
Once a square is written, its value does not change. Therefore, the first few squares would receive the following values:
147 142 133 122 59
304 5 4 2 57
330 10 1 1 54
351 11 23 25 26
362 747 806---> ...
What is the first value written that is larger than your puzzle input?
```
For this part of the puzzle, it seemed easiest to me to simply go through and build this spiral up from the ground. To start, we need to understand how the spiral moves in terms of coordinates. If we take the center position to be (0, 0), then the next position is (1, 0), then (1, 1), and so on:
```
17 16 15 14 13
18 5 4 3 12
19 6 1 2 11
20 7 8 9 10
21 22 23---> ...
1 = (0, 0)
2 = (1, 0)
3 = (1, 1)
4 = (0, 1)
5 = (-1, 1)
6 = (-1, 0)
7 = (-1, -1)
8 = (0, -1)
9 = (1, -1)
10 = (2, -1)
11 = (2, 0)
12 = (2, 1)
13 = (2, 2)
14 = (1, 2)
15 = (0, 2)
```
Let's write it out as a data frame:
```{r}
df <- data.frame(position = 1:15,
x = c(0,1,1,0,-1,-1,-1,0,1,2,2,2,2,1,0),
y = c(0,0,1,1,1,0,-1,-1,-1,-1,0,1,2,2,2))
df
```
Now let's look at the change in x and y:
```{r}
df %>%
mutate(start_x = c(NA, x[-15]),
start_y = c(NA, y[-15]),
diff_x = x - start_x,
diff_y = y - start_y) %>%
select(position, diff_x, diff_y)
```
From this, we can see clearly that:
1. Every change in position is at most 1 step
2. When x changes, y does not change (and vice versa)
3. The number of steps flips between series of +1's and -1's (with zeros in between)
4. The number of repeats expands by one each time: one 1, one 0, two -1's, two 0's, three 1's, three 0's....
I decided to take advantage of these patterns to build up a spiral, step-by-step. I went the for-loop route the first time around:
```{r}
total_x <- NULL
for(i in 1:100){
vector <- c(rep((-1)^(i+1), times = i),
rep(0, times = i))
# i + 1 helps us alternate between +1 and -1
# we increase the number of times it repeats by one each time we loop
total_x <- c(total_x, vector)
}
total_y <- NULL
for(i in 1:100){
vector <- c(rep(0, i), rep((-1)^(i+1), i)) #start with 0 for y
total_y <- c(total_y, vector)
}
xy <- data.frame(x = c(0,total_x),
y = c(0,total_y))
head(xy)
```
Upon returning to this, I tidyversed it:
```{r}
xy <- tibble(i = 1:100) %>%
rowwise() %>%
mutate(x = str_c(c(rep((-1)^(i+1), i), rep(0, i)),
collapse = " "),
y = str_c(c(rep(0, i), rep((-1)^(i+1), i)),
collapse = " ")) %>%
separate_rows(x:y, sep = " ", convert = TRUE)
head(xy)
```
Let's test this out on a small matrix to make sure the spiral builds correctly.
```{r}
grid <- matrix(nrow = 5, ncol = 5)
i = 3; j = 3 #starting point (origin)
for(n in 1:10){
i <- i - xy$y[n]
j <- j + xy$x[n]
grid[i, j] <- n
print(grid) #just to see that we're doing this right
}
```
It seems to work! Now instead of inputting the step number to the grid, let's do some addition:
```{r}
grid <- matrix(nrow = 7, ncol = 7) #start with a slightly larger grid
i = 4; j = 4 #origin coordinates
grid[i, j] <- 1 #define origin as 1
for(n in 2:20){ #starts at 2 since we already defined the origin
i <- i - xy$y[n]
j <- j + xy$x[n]
#get sum of neighbors
neighbors <- grid[(i - 1):(i + 1), (j - 1):(j + 1)]
sum_neighbors <- sum(neighbors, na.rm = TRUE)
grid[i, j] <- sum_neighbors
}
```
Again, it seems to work! Now to answer the puzzle, I'll expand the grid and also save the output as a vector.
```{r}
grid <- matrix(nrow = 1001, ncol = 1001)
i = 501; j = 501
grid[i, j] <- 1
v_sum_neighbors <- vector(length = 1000)
for(n in 2:1000){
i <- i - xy$y[n]
j <- j + xy$x[n]
neighbors <- grid[(i - 1):(i + 1), (j - 1):(j + 1)]
sum_neighbors <- sum(neighbors, na.rm = TRUE)
grid[i, j] <- sum_neighbors
v_sum_neighbors[n] <- sum_neighbors
}
```
Now we're ready to answer the puzzle question: What is the first value written that is larger than your puzzle input?
```{r}
puzzle_input <- 312051
head(v_sum_neighbors[v_sum_neighbors > puzzle_input], 1)
```
Voila! and that's it! Another challenge down. :trophy: