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 {:nx, github: "elixir-nx/nx", sparse: "nx"} 13]) 14``` 15 16## Day 1 17 18### Load input 19 20```elixir 21stream = 22 File.stream!("day1.txt") 23 |> Stream.map(&String.to_integer(String.trim(&1))) 24``` 25 26### Task 1 27 28<!-- livebook:{"break_markdown":true} --> 29 30Compute count of consecutive increases 31 32```elixir 33stream 34|> Stream.chunk_every(2, 1, :discard) 35|> Enum.count(fn [a, b] -> a < b end) 36``` 37 38### Task 2 39 40<!-- livebook:{"break_markdown":true} --> 41 42Compute count of consecutive increases of sums of trigrams. 43 44However we can notice, that if we have list like: 45 46$$ 47[a, b, c, d] 48$$ 49 50Then when we want to compare consecutive trigrams then we compare: 51 52$$ 53a + b + c < b + c + d \\ 54a < d 55$$ 56 57So we can traverse each 4 elements and then just compare first and last one 58instead of summing and then traversing it again. 59 60```elixir 61stream 62|> Stream.chunk_every(4, 1, :discard) 63|> Enum.count(fn [a, _, _, b] -> a < b end) 64``` 65 66## Day 2 67 68### Load input 69 70We do parsing there, as it will help us with the latter tasks. Pattern matching 71is the simplest approach there, as input is in form of: 72 73``` 74forward 10 75up 20 76down 30 77``` 78 79We need to `trim/1` input to make sure that the last newline will not interrupt 80`String.to_integer/1` calls. 81 82```elixir 83stream = 84 File.stream!("day2.txt") 85 |> Stream.map(fn input -> 86 case String.trim(input) do 87 "forward " <> n -> {:forward, String.to_integer(n)} 88 "up " <> n -> {:up, String.to_integer(n)} 89 "down " <> n -> {:down, String.to_integer(n)} 90 end 91 end) 92``` 93 94### Task 1 95 96```elixir 97{h, d} = 98 stream 99 |> Enum.reduce({0, 0}, fn 100 {:forward, n}, {h, d} -> {h + n, d} 101 {:up, n}, {h, d} -> {h, d - n} 102 {:down, n}, {h, d} -> {h, d + n} 103 end) 104 105h * d 106``` 107 108### Task 2 109 110```elixir 111{h, d, _} = 112 stream 113 |> Enum.reduce({0, 0, 0}, fn 114 {:forward, n}, {h, d, a} -> {h + n, d + a * n, a} 115 {:up, n}, {h, d, a} -> {h, d, a - n} 116 {:down, n}, {h, d, a} -> {h, d, a + n} 117 end) 118 119h * d 120``` 121 122## Day 3 123 124### Input 125 126```elixir 127stream = 128 File.stream!("day3.txt") 129 |> Enum.map(&String.trim/1) 130 |> Enum.map(&String.to_charlist/1) 131 132defmodule Day3 do 133 def count(list) do 134 Enum.reduce(list, List.duplicate(0, 12), fn input, acc -> 135 for {value, counter} <- Enum.zip(input, acc) do 136 case value do 137 ?1 -> counter + 1 138 ?0 -> counter 139 end 140 end 141 end) 142 end 143end 144``` 145 146### Task 1 147 148```elixir 149half = div(length(stream), 2) 150 151{a, b} = 152 stream 153 |> Day3.count() 154 |> Enum.reduce({0, 0}, fn elem, {a, b} -> 155 if elem > half do 156 {a * 2 + 1, b * 2} 157 else 158 {a * 2, b * 2 + 1} 159 end 160 end) 161 162a * b 163``` 164 165### Task 2 166 167```elixir 168defmodule Day3.Task2 do 169 def reduce(list, cb), do: reduce(list, 0, cb) 170 171 defp reduce([elem], _, _), do: elem 172 173 defp reduce(list, at, cb) do 174 counts = Day3.count(list) 175 176 half = div(length(list), 2) 177 count = Enum.at(counts, at) 178 179 bit = 180 cond do 181 count == half and cb.(count + 1, half) -> ?1 182 count != half and cb.(count, half) -> ?1 183 true -> ?0 184 end 185 186 reduce(Enum.filter(list, &(Enum.at(&1, at) == bit)), at + 1, cb) 187 end 188end 189 190co2 = List.to_integer(Day3.Task2.reduce(stream, &</2), 2) 191o2 = List.to_integer(Day3.Task2.reduce(stream, &>/2), 2) 192 193co2 * o2 194``` 195 196## Day 4 197 198### Input 199 200This time it is a little bit more convoluted, as there are 2 parts of the input. 201Fortunately we can easily disect the parts via pattern matching. 202 203Technically the conversion to the numbers is not needed, but it does no harm 204and provides additional layer of safety against some whitespace characters left there 205and here. 206 207The `Day4.win/2` function is manually unrolled, as it is easier to write than some 208random jumping in the list. 209 210<!-- livebook:{"disable_formatting":true} --> 211 212```elixir 213[numbers | bingos] = 214 File.read!("day4.txt") 215 |> String.split("\n\n", trim: true) 216 217numbers = 218 numbers 219 |> String.trim() 220 |> String.split(",") 221 |> Enum.map(&String.to_integer/1) 222 223bingos = 224 bingos 225 |> Enum.map(fn bingo -> 226 bingo 227 |> String.split(~r/\s+/, trim: true) 228 |> Enum.map(&String.to_integer/1) 229 end) 230 231defmodule Day4 do 232 def win( 233 [ 234 a1, a2, a3, a4, a5, 235 b1, b2, b3, b4, b5, 236 c1, c2, c3, c4, c5, 237 d1, d2, d3, d4, d5, 238 e1, e2, e3, e4, e5 239 ], 240 nums 241 ) do 242 # Rows 243 all_in([a1, a2, a3, a4, a5], nums) or 244 all_in([b1, b3, b3, b4, b5], nums) or 245 all_in([c1, c2, c3, c4, c5], nums) or 246 all_in([d1, d2, d3, d4, d5], nums) or 247 all_in([e1, e2, e3, e4, e5], nums) or 248 # Columns 249 all_in([a1, b1, c1, d1, e1], nums) or 250 all_in([a2, b2, c2, d2, e2], nums) or 251 all_in([a3, b3, c3, d3, e3], nums) or 252 all_in([a4, b4, c4, d4, e4], nums) or 253 all_in([a5, b5, c5, d5, e5], nums) 254 end 255 256 def not_matched(bingo, nums) do 257 Enum.reject(bingo, &(&1 in nums)) 258 end 259 260 defp all_in(list, nums) do 261 Enum.all?(list, &(&1 in nums)) 262 end 263end 264``` 265 266### Task 1 267 268We simply traverse the `numbers` list aggregating the numbers (order doesn't really matter, 269here we aggregate them in reverse order to speedup the code). When we have enough numbers 270that any of the `bingos` is winning one, then we halt the reduction and return computed 271result. 272 273```elixir 274numbers 275|> Enum.reduce_while([], fn elem, acc -> 276 matches = [elem | acc] 277 278 case Enum.find(bingos, &Day4.win(&1, matches)) do 279 nil -> {:cont, matches} 280 bingo -> {:halt, Enum.sum(Day4.not_matched(bingo, matches)) * elem} 281 end 282end) 283``` 284 285### Task 2 286 287```elixir 288numbers 289|> Enum.reduce_while({bingos, []}, fn elem, {bingos, acc} -> 290 matches = [elem | acc] 291 292 case bingos do 293 [bingo] -> 294 if Day4.win(bingo, matches) do 295 {:halt, Enum.sum(Day4.not_matched(bingo, matches)) * elem} 296 else 297 {:cont, {bingos, matches}} 298 end 299 300 _ -> 301 {:cont, {Enum.reject(bingos, &Day4.win(&1, matches)), matches}} 302 end 303end) 304``` 305 306## Day 5 307 308```elixir 309defmodule Day5 do 310 defmodule Point do 311 defstruct [:x, :y] 312 313 def parse(input) do 314 [x, y] = String.split(input, ",") 315 316 %__MODULE__{x: String.to_integer(x), y: String.to_integer(y)} 317 end 318 end 319 320 defmodule Line do 321 defstruct [:start, :finish] 322 323 def new(a, b) do 324 {start, finish} = 325 cond do 326 a.x < b.x -> {a, b} 327 a.y < b.y -> {a, b} 328 true -> {b, a} 329 end 330 331 %__MODULE__{start: start, finish: finish} 332 end 333 334 def horizontal?(a), do: a.start.y == a.finish.y 335 def vertical?(a), do: a.start.x == a.finish.x 336 337 def intersect?(a, b) do 338 a.start.x in b.start.x..b.finish.y and b.start.y in a.start.y..a.finish.y 339 end 340 341 def overlapping?(a, b) do 342 {a.start.x, a.finish.x} == {b.start.x, b.finish.x} or 343 {a.start.y, a.finish.y} == {b.start.y, b.start.y} 344 end 345 346 def orientation(a) do 347 cond do 348 horizontal?(a) -> :horizontal 349 vertical?(a) -> :vertical 350 true -> :diagonal 351 end 352 end 353 end 354end 355 356lines = 357 File.stream!("day5.txt") 358 |> Stream.map(&String.trim/1) 359 |> Stream.map(fn input -> 360 [a, b] = String.split(input, " -> ") 361 362 pa = Day5.Point.parse(a) 363 pb = Day5.Point.parse(b) 364 365 Day5.Line.new(pa, pb) 366 end) 367 368x_min = 369 lines 370 |> Enum.map(& &1.start.x) 371 |> Enum.min() 372 373x_max = 374 lines 375 |> Enum.map(& &1.finish.x) 376 |> Enum.max() 377 378y_min = 379 lines 380 |> Enum.map(& &1.start.y) 381 |> Enum.min() 382 383y_max = 384 lines 385 |> Enum.map(& &1.finish.y) 386 |> Enum.max() 387 388{x_min, x_max, y_min, y_max} 389 390arr = Nx.iota({1000, 1000}) 391 392arr = Nx.subtract(arr, arr) 393 394lines 395|> Enum.flat_map(fn a -> 396 case Day5.Line.orientation(a) do 397 :diagonal -> [(a.start.x - a.finish.x) / (a.start.y - a.finish.y)] 398 _ -> [] 399 end 400end) 401``` 402 403### Task 1 404 405```elixir 406%{horizontal: horiz, vertical: vert} = Enum.group_by(lines, &Day5.Line.orientation/1) 407 408horiz = Enum.sort_by(horiz, &{&1.start.x, &1.start.y}) 409vert = Enum.sort_by(vert, & &1.start.x) 410``` 411 412## Day 6 413 414```elixir 415initial = for i <- 0..8, into: %{}, do: {i, 0} 416 417counts = 418 File.read!("day6.txt") 419 |> String.trim() 420 |> String.split(",") 421 |> Enum.map(&String.to_integer/1) 422 |> Enum.frequencies() 423 |> Map.merge(initial, fn _, a, _ -> a end) 424``` 425 426### Task 1 427 428```elixir 4291..80 430|> Enum.reduce(counts, fn _, %{0 => next} = acc -> 431 1..8 432 |> Map.new(fn idx -> {idx - 1, acc[idx]} end) 433 |> Map.update!(6, &(&1 + next)) 434 |> Map.put(8, next) 435end) 436|> Map.values() 437|> Enum.sum() 438``` 439 440### Task 2 441 442```elixir 4431..256 444|> Enum.reduce(counts, fn _, %{0 => next} = acc -> 445 1..8 446 |> Map.new(fn idx -> {idx - 1, acc[idx]} end) 447 |> Map.update!(6, &(&1 + next)) 448 |> Map.put(8, next) 449end) 450|> Map.values() 451|> Enum.sum() 452```