livebook:{"persist_outputs":true} --> # Advent of Code 2021 ## Setup ```elixir Mix.install([]) ``` ## Day 1 ### Load input ```elixir stream = File.stream!("day1.txt") |> Stream.map(&String.to_integer(String.trim(&1))) ``` ### Task 1 Compute count of consecutive increases ```elixir stream |> Stream.chunk_every(2, 1, :discard) |> Enum.count(fn [a, b] -> a < b end) ``` ### Task 2 Compute count of consecutive increases of sums of trigrams. However we can notice, that if we have list like: $$ [a, b, c, d] $$ Then when we want to compare consecutive trigrams then we compare: $$ a + b + c < b + c + d \\ a < d $$ So we can traverse each 4 elements and then just compare first and last one instead of summing and then traversing it again. ```elixir stream |> Stream.chunk_every(4, 1, :discard) |> Enum.count(fn [a, _, _, b] -> a < b end) ``` ## Day 2 ### Load input We do parsing there, as it will help us with the latter tasks. Pattern matching is the simplest approach there, as input is in form of: ``` forward 10 up 20 down 30 ``` We need to `trim/1` input to make sure that the last newline will not interrupt `String.to_integer/1` calls. ```elixir stream = File.stream!("day2.txt") |> Stream.map(fn input -> case String.trim(input) do "forward " <> n -> {:forward, String.to_integer(n)} "up " <> n -> {:up, String.to_integer(n)} "down " <> n -> {:down, String.to_integer(n)} end end) ``` ### Task 1 ```elixir {h, d} = stream |> Enum.reduce({0, 0}, fn {:forward, n}, {h, d} -> {h + n, d} {:up, n}, {h, d} -> {h, d - n} {:down, n}, {h, d} -> {h, d + n} end) h * d ``` ### Task 2 ```elixir {h, d, _} = stream |> Enum.reduce({0, 0, 0}, fn {:forward, n}, {h, d, a} -> {h + n, d + a * n, a} {:up, n}, {h, d, a} -> {h, d, a - n} {:down, n}, {h, d, a} -> {h, d, a + n} end) h * d ``` ## Day 3 ### Input ```elixir stream = File.stream!("day3.txt") |> Enum.map(&String.trim/1) |> Enum.map(&String.to_charlist/1) defmodule Day3 do def count(list) do Enum.reduce(list, List.duplicate(0, 12), fn input, acc -> for {value, counter} <- Enum.zip(input, acc) do case value do ?1 -> counter + 1 ?0 -> counter end end end) end end ``` ### Task 1 ```elixir half = div(length(stream), 2) {a, b} = stream |> Day3.count() |> Enum.reduce({0, 0}, fn elem, {a, b} -> if elem > half do {a * 2 + 1, b * 2} else {a * 2, b * 2 + 1} end end) a * b ``` ### Task 2 ```elixir defmodule Day3.Task2 do def reduce(list, cb), do: reduce(list, 0, cb) defp reduce([elem], _, _), do: elem defp reduce(list, at, cb) do counts = Day3.count(list) half = div(length(list), 2) count = Enum.at(counts, at) bit = cond do count == half and cb.(count + 1, half) -> ?1 count != half and cb.(count, half) -> ?1 true -> ?0 end reduce(Enum.filter(list, &(Enum.at(&1, at) == bit)), at + 1, cb) end end co2 = List.to_integer(Day3.Task2.reduce(stream, &/2), 2) co2 * o2 ``` ## Day 4 ### Input This time it is a little bit more convoluted, as there are 2 parts of the input. Fortunately we can easily disect the parts via pattern matching. Technically the conversion to the numbers is not needed, but it does no harm and provides additional layer of safety against some whitespace characters left there and here. The `Day4.win/2` function is manually unrolled, as it is easier to write than some random jumping in the list. ```elixir [numbers | bingos] = File.read!("day4.txt") |> String.split("\n\n", trim: true) numbers = numbers |> String.trim() |> String.split(",") |> Enum.map(&String.to_integer/1) bingos = bingos |> Enum.map(fn bingo -> bingo |> String.split(~r/\s+/, trim: true) |> Enum.map(&String.to_integer/1) end) defmodule Day4 do def win( [ a1, a2, a3, a4, a5, b1, b2, b3, b4, b5, c1, c2, c3, c4, c5, d1, d2, d3, d4, d5, e1, e2, e3, e4, e5 ], nums ) do # Rows all_in([a1, a2, a3, a4, a5], nums) or all_in([b1, b3, b3, b4, b5], nums) or all_in([c1, c2, c3, c4, c5], nums) or all_in([d1, d2, d3, d4, d5], nums) or all_in([e1, e2, e3, e4, e5], nums) or # Columns all_in([a1, b1, c1, d1, e1], nums) or all_in([a2, b2, c2, d2, e2], nums) or all_in([a3, b3, c3, d3, e3], nums) or all_in([a4, b4, c4, d4, e4], nums) or all_in([a5, b5, c5, d5, e5], nums) or # Diagonals all_in([a1, b2, c3, d4, e5], nums) or all_in([a5, b4, c3, d2, e1], nums) end def not_matched(bingo, nums) do Enum.reject(bingo, &(&1 in nums)) end defp all_in(list, nums) do Enum.all?(list, &(&1 in nums)) end end ``` ### Task 1 We simply traverse the `numbers` list aggregating the numbers (order doesn't really matter, here we aggregate them in reverse order to speedup the code). When we have enough numbers that any of the `bingos` is winning one, then we halt the reduction and return computed result. ```elixir numbers |> Enum.reduce_while([], fn elem, acc -> matches = [elem | acc] case Enum.find(bingos, &Day4.win(&1, matches)) do nil -> {:cont, matches} bingo -> {:halt, Enum.sum(Day4.not_matched(bingo, matches)) * elem} end end) ``` ### Task 2 ```elixir numbers |> Enum.reduce_while({bingos, []}, fn elem, {bingos, acc} -> matches = [elem | acc] case bingos do [bingo] -> if Day4.win(bingo, matches) do {:halt, Enum.sum(Day4.not_matched(bingo, matches)) * elem} else {:cont, {bingos, matches}} end _ -> {:cont, {Enum.reject(bingos, &Day4.win(&1, matches)), matches}} end end) ```