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```