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)
252 end
253
254 def not_matched(bingo, nums) do
255 Enum.reject(bingo, &(&1 in nums))
256 end
257
258 defp all_in(list, nums) do
259 Enum.all?(list, &(&1 in nums))
260 end
261end
262```
263
264### Task 1
265
266We simply traverse the `numbers` list aggregating the numbers (order doesn't really matter,
267here we aggregate them in reverse order to speedup the code). When we have enough numbers
268that any of the `bingos` is winning one, then we halt the reduction and return computed
269result.
270
271```elixir
272numbers
273|> Enum.reduce_while([], fn elem, acc ->
274 matches = [elem | acc]
275
276 case Enum.find(bingos, &Day4.win(&1, matches)) do
277 nil -> {:cont, matches}
278 bingo -> {:halt, Enum.sum(Day4.not_matched(bingo, matches)) * elem}
279 end
280end)
281```
282
283### Task 2
284
285```elixir
286numbers
287|> Enum.reduce_while({bingos, []}, fn elem, {bingos, acc} ->
288 matches = [elem | acc]
289
290 case bingos do
291 [bingo] ->
292 if Day4.win(bingo, matches) do
293 {:halt, Enum.sum(Day4.not_matched(bingo, matches)) * elem}
294 else
295 {:cont, {bingos, matches}}
296 end
297
298 _ ->
299 {:cont, {Enum.reject(bingos, &Day4.win(&1, matches)), matches}}
300 end
301end)
302```
303
304## Day 5