this repo has no description
1<!--
2 vim:ft=markdown --!>
3 livebook:{"persist_outputs":true}
4-->
5
6# Advent of Code 2021
7
8## Setup
9
10```elixir
11Mix.install([])
12```
13
14## Day 1
15
16### Load input
17
18```elixir
19stream =
20 File.stream!("day1.txt")
21 |> Stream.map(&String.to_integer(String.trim(&1)))
22```
23
24### Task 1
25
26<!-- livebook:{"break_markdown":true} -->
27
28Compute count of consecutive increases
29
30```elixir
31stream
32|> Stream.chunk_every(2, 1, :discard)
33|> Enum.count(fn [a, b] -> a < b end)
34```
35
36### Task 2
37
38<!-- livebook:{"break_markdown":true} -->
39
40Compute count of consecutive increases of sums of trigrams.
41
42However we can notice, that if we have list like:
43
44$$
45[a, b, c, d]
46$$
47
48Then when we want to compare consecutive trigrams then we compare:
49
50$$
51a + b + c < b + c + d \\
52a < d
53$$
54
55So we can traverse each 4 elements and then just compare first and last one
56instead of summing and then traversing it again.
57
58```elixir
59stream
60|> Stream.chunk_every(4, 1, :discard)
61|> Enum.count(fn [a, _, _, b] -> a < b end)
62```
63
64## Day 2
65
66### Load input
67
68We do parsing there, as it will help us with the latter tasks. Pattern matching
69is the simplest approach there, as input is in form of:
70
71```
72forward 10
73up 20
74down 30
75```
76
77We need to `trim/1` input to make sure that the last newline will not interrupt
78`String.to_integer/1` calls.
79
80```elixir
81stream =
82 File.stream!("day2.txt")
83 |> Stream.map(fn input ->
84 case String.trim(input) do
85 "forward " <> n -> {:forward, String.to_integer(n)}
86 "up " <> n -> {:up, String.to_integer(n)}
87 "down " <> n -> {:down, String.to_integer(n)}
88 end
89 end)
90```
91
92### Task 1
93
94```elixir
95{h, d} =
96 stream
97 |> Enum.reduce({0, 0}, fn
98 {:forward, n}, {h, d} -> {h + n, d}
99 {:up, n}, {h, d} -> {h, d - n}
100 {:down, n}, {h, d} -> {h, d + n}
101 end)
102
103h * d
104```
105
106### Task 2
107
108```elixir
109{h, d, _} =
110 stream
111 |> Enum.reduce({0, 0, 0}, fn
112 {:forward, n}, {h, d, a} -> {h + n, d + a * n, a}
113 {:up, n}, {h, d, a} -> {h, d, a - n}
114 {:down, n}, {h, d, a} -> {h, d, a + n}
115 end)
116
117h * d
118```
119
120## Day 3
121
122### Input
123
124```elixir
125stream =
126 File.stream!("day3.txt")
127 |> Enum.map(&String.trim/1)
128 |> Enum.map(&String.to_charlist/1)
129
130defmodule Day3 do
131 def count(list) do
132 Enum.reduce(list, List.duplicate(0, 12), fn input, acc ->
133 for {value, counter} <- Enum.zip(input, acc) do
134 case value do
135 ?1 -> counter + 1
136 ?0 -> counter
137 end
138 end
139 end)
140 end
141end
142```
143
144### Task 1
145
146```elixir
147half = div(length(stream), 2)
148
149{a, b} =
150 stream
151 |> Day3.count()
152 |> Enum.reduce({0, 0}, fn elem, {a, b} ->
153 if elem > half do
154 {a * 2 + 1, b * 2}
155 else
156 {a * 2, b * 2 + 1}
157 end
158 end)
159
160a * b
161```
162
163### Task 2
164
165```elixir
166defmodule Day3.Task2 do
167 def reduce(list, cb), do: reduce(list, 0, cb)
168
169 defp reduce([elem], _, _), do: elem
170
171 defp reduce(list, at, cb) do
172 counts = Day3.count(list)
173
174 half = div(length(list), 2)
175 count = Enum.at(counts, at)
176
177 bit =
178 cond do
179 count == half and cb.(count + 1, half) -> ?1
180 count != half and cb.(count, half) -> ?1
181 true -> ?0
182 end
183
184 reduce(Enum.filter(list, &(Enum.at(&1, at) == bit)), at + 1, cb)
185 end
186end
187
188co2 = List.to_integer(Day3.Task2.reduce(stream, &</2), 2)
189o2 = List.to_integer(Day3.Task2.reduce(stream, &>/2), 2)
190
191co2 * o2
192```
193
194## Day 4
195
196### Input
197
198This time it is a little bit more convoluted, as there are 2 parts of the input.
199Fortunately we can easily disect the parts via pattern matching.
200
201Technically the conversion to the numbers is not needed, but it does no harm
202and provides additional layer of safety against some whitespace characters left there
203and here.
204
205The `Day4.win/2` function is manually unrolled, as it is easier to write than some
206random jumping in the list.
207
208<!-- livebook:{"disable_formatting":true} -->
209
210```elixir
211[numbers | bingos] =
212 File.read!("day4.txt")
213 |> String.split("\n\n", trim: true)
214
215numbers =
216 numbers
217 |> String.trim()
218 |> String.split(",")
219 |> Enum.map(&String.to_integer/1)
220
221bingos =
222 bingos
223 |> Enum.map(fn bingo ->
224 bingo
225 |> String.split(~r/\s+/, trim: true)
226 |> Enum.map(&String.to_integer/1)
227 end)
228
229defmodule Day4 do
230 def win(
231 [
232 a1, a2, a3, a4, a5,
233 b1, b2, b3, b4, b5,
234 c1, c2, c3, c4, c5,
235 d1, d2, d3, d4, d5,
236 e1, e2, e3, e4, e5
237 ],
238 nums
239 ) do
240 # Rows
241 all_in([a1, a2, a3, a4, a5], nums) or
242 all_in([b1, b3, b3, b4, b5], nums) or
243 all_in([c1, c2, c3, c4, c5], nums) or
244 all_in([d1, d2, d3, d4, d5], nums) or
245 all_in([e1, e2, e3, e4, e5], nums) or
246 # Columns
247 all_in([a1, b1, c1, d1, e1], nums) or
248 all_in([a2, b2, c2, d2, e2], nums) or
249 all_in([a3, b3, c3, d3, e3], nums) or
250 all_in([a4, b4, c4, d4, e4], nums) or
251 all_in([a5, b5, c5, d5, e5], nums) or
252 # Diagonals
253 all_in([a1, b2, c3, d4, e5], nums) or
254 all_in([a5, b4, c3, d2, e1], nums)
255 end
256
257 def not_matched(bingo, nums) do
258 Enum.reject(bingo, &(&1 in nums))
259 end
260
261 defp all_in(list, nums) do
262 Enum.all?(list, &(&1 in nums))
263 end
264end
265```
266
267### Task 1
268
269We simply traverse the `numbers` list aggregating the numbers (order doesn't really matter,
270here we aggregate them in reverse order to speedup the code). When we have enough numbers
271that any of the `bingos` is winning one, then we halt the reduction and return computed
272result.
273
274```elixir
275numbers
276|> Enum.reduce_while([], fn elem, acc ->
277 matches = [elem | acc]
278
279 case Enum.find(bingos, &Day4.win(&1, matches)) do
280 nil -> {:cont, matches}
281 bingo -> {:halt, Enum.sum(Day4.not_matched(bingo, matches)) * elem}
282 end
283end)
284```
285
286### Task 2
287
288```elixir
289numbers
290|> Enum.reduce_while({bingos, []}, fn elem, {bingos, acc} ->
291 matches = [elem | acc]
292
293 case bingos do
294 [bingo] ->
295 if Day4.win(bingo, matches) do
296 {:halt, Enum.sum(Day4.not_matched(bingo, matches)) * elem}
297 else
298 {:cont, {bingos, matches}}
299 end
300
301 _ ->
302 {:cont, {Enum.reject(bingos, &Day4.win(&1, matches)), matches}}
303 end
304end)
305```