this repo has no description
1<!-- 2 vim:ft=markdown --!> 3 livebook:{"persist_outputs":true} 4--> 5 6<!-- livebook:{"persist_outputs":true} --> 7 8# Advent of Code 2021 9 10## Setup 11 12```elixir 13Mix.install([]) 14``` 15 16```output 17:ok 18``` 19 20## Day 1 21 22### Load input 23 24```elixir 25stream = 26 File.stream!("day1.txt") 27 |> Stream.map(&String.to_integer(String.trim(&1))) 28``` 29 30```output 31#Stream<[ 32 enum: %File.Stream{ 33 line_or_bytes: :line, 34 modes: [:raw, :read_ahead, :binary], 35 path: "day1.txt", 36 raw: true 37 }, 38 funs: [#Function<47.58486609/1 in Stream.map/2>] 39]> 40``` 41 42### Task 1 43 44<!-- livebook:{"break_markdown":true} --> 45 46Compute count of consecutive increases 47 48```elixir 49stream 50|> Stream.chunk_every(2, 1, :discard) 51|> Enum.count(fn [a, b] -> a < b end) 52``` 53 54```output 551688 56``` 57 58### Task 2 59 60<!-- livebook:{"break_markdown":true} --> 61 62Compute count of consecutive increases of sums of trigrams. 63 64However we can notice, that if we have list like: 65 66$$ 67[a, b, c, d] 68$$ 69 70Then when we want to compare consecutive trigrams then we compare: 71 72$$ 73a + b + c < b + c + d \\ 74a < d 75$$ 76 77So we can traverse each 4 elements and then just compare first and last one 78instead of summing and then traversing it again. 79 80```elixir 81stream 82|> Stream.chunk_every(4, 1, :discard) 83|> Enum.count(fn [a, _, _, b] -> a < b end) 84``` 85 86```output 871728 88``` 89 90## Day 2 91 92### Load input 93 94We do parsing there, as it will help us with the latter tasks. Pattern matching 95is the simplest approach there, as input is in form of: 96 97``` 98forward 10 99up 20 100down 30 101``` 102 103We need to `trim/1` input to make sure that the last newline will not interrupt 104`String.to_integer/1` calls. 105 106```elixir 107stream = 108 File.stream!("day2.txt") 109 |> Stream.map(fn input -> 110 case String.trim(input) do 111 "forward " <> n -> {:forward, String.to_integer(n)} 112 "up " <> n -> {:up, String.to_integer(n)} 113 "down " <> n -> {:down, String.to_integer(n)} 114 end 115 end) 116``` 117 118```output 119#Stream<[ 120 enum: %File.Stream{ 121 line_or_bytes: :line, 122 modes: [:raw, :read_ahead, :binary], 123 path: "day2.txt", 124 raw: true 125 }, 126 funs: [#Function<47.58486609/1 in Stream.map/2>] 127]> 128``` 129 130### Task 1 131 132```elixir 133{h, d} = 134 stream 135 |> Enum.reduce({0, 0}, fn 136 {:forward, n}, {h, d} -> {h + n, d} 137 {:up, n}, {h, d} -> {h, d - n} 138 {:down, n}, {h, d} -> {h, d + n} 139 end) 140 141h * d 142``` 143 144```output 1451499229 146``` 147 148### Task 2 149 150```elixir 151{h, d, _} = 152 stream 153 |> Enum.reduce({0, 0, 0}, fn 154 {:forward, n}, {h, d, a} -> {h + n, d + a * n, a} 155 {:up, n}, {h, d, a} -> {h, d, a - n} 156 {:down, n}, {h, d, a} -> {h, d, a + n} 157 end) 158 159h * d 160``` 161 162```output 1631340836560 164``` 165 166## Day 3 167 168### Input 169 170```elixir 171stream = 172 File.stream!("day3.txt") 173 |> Enum.map(&String.trim/1) 174 |> Enum.map(&String.to_charlist/1) 175 176defmodule Day3 do 177 def count(list) do 178 Enum.reduce(list, List.duplicate(0, 12), fn input, acc -> 179 for {value, counter} <- Enum.zip(input, acc) do 180 case value do 181 ?1 -> counter + 1 182 ?0 -> counter 183 end 184 end 185 end) 186 end 187end 188``` 189 190```output 191{:module, Day3, <<70, 79, 82, 49, 0, 0, 7, ...>>, {:count, 1}} 192``` 193 194### Task 1 195 196```elixir 197half = div(length(stream), 2) 198 199{a, b} = 200 stream 201 |> Day3.count() 202 |> Enum.reduce({0, 0}, fn elem, {a, b} -> 203 if elem > half do 204 {a * 2 + 1, b * 2} 205 else 206 {a * 2, b * 2 + 1} 207 end 208 end) 209 210a * b 211``` 212 213```output 2143847100 215``` 216 217### Task 2 218 219```elixir 220defmodule Day3.Task2 do 221 def reduce(list, cb), do: reduce(list, 0, cb) 222 223 defp reduce([elem], _, _), do: elem 224 225 defp reduce(list, at, cb) do 226 counts = Day3.count(list) 227 228 half = div(length(list), 2) 229 count = Enum.at(counts, at) 230 231 bit = 232 cond do 233 count == half and cb.(count + 1, half) -> ?1 234 count != half and cb.(count, half) -> ?1 235 true -> ?0 236 end 237 238 reduce(Enum.filter(list, &(Enum.at(&1, at) == bit)), at + 1, cb) 239 end 240end 241 242co2 = List.to_integer(Day3.Task2.reduce(stream, &</2), 2) 243o2 = List.to_integer(Day3.Task2.reduce(stream, &>/2), 2) 244 245co2 * o2 246``` 247 248```output 2494105235 250``` 251 252## Day 4 253 254### Input 255 256This time it is a little bit more convoluted, as there are 2 parts of the input. 257Fortunately we can easily disect the parts via pattern matching. 258 259Technically the conversion to the numbers is not needed, but it does no harm 260and provides additional layer of safety against some whitespace characters left there 261and here. 262 263The `Day4.win/2` function is manually unrolled, as it is easier to write than some 264random jumping in the list. 265 266<!-- livebook:{"disable_formatting":true} --> 267 268```elixir 269[numbers | bingos] = 270 File.read!("day4.txt") 271 |> String.split("\n\n", trim: true) 272 273numbers = 274 numbers 275 |> String.trim() 276 |> String.split(",") 277 |> Enum.map(&String.to_integer/1) 278 279bingos = 280 bingos 281 |> Enum.map(fn bingo -> 282 bingo 283 |> String.split(~r/\s+/, trim: true) 284 |> Enum.map(&String.to_integer/1) 285 end) 286 287defmodule Day4 do 288 def win( 289 [ 290 a1, a2, a3, a4, a5, 291 b1, b2, b3, b4, b5, 292 c1, c2, c3, c4, c5, 293 d1, d2, d3, d4, d5, 294 e1, e2, e3, e4, e5 295 ], 296 nums 297 ) do 298 # Rows 299 all_in([a1, a2, a3, a4, a5], nums) or 300 all_in([b1, b3, b3, b4, b5], nums) or 301 all_in([c1, c2, c3, c4, c5], nums) or 302 all_in([d1, d2, d3, d4, d5], nums) or 303 all_in([e1, e2, e3, e4, e5], nums) or 304 # Columns 305 all_in([a1, b1, c1, d1, e1], nums) or 306 all_in([a2, b2, c2, d2, e2], nums) or 307 all_in([a3, b3, c3, d3, e3], nums) or 308 all_in([a4, b4, c4, d4, e4], nums) or 309 all_in([a5, b5, c5, d5, e5], nums) 310 end 311 312 def not_matched(bingo, nums) do 313 Enum.reject(bingo, &(&1 in nums)) 314 end 315 316 defp all_in(list, nums) do 317 Enum.all?(list, &(&1 in nums)) 318 end 319end 320``` 321 322```output 323{:module, Day4, <<70, 79, 82, 49, 0, 0, 15, ...>>, {:all_in, 2}} 324``` 325 326### Task 1 327 328We simply traverse the `numbers` list aggregating the numbers (order doesn't really matter, 329here we aggregate them in reverse order to speedup the code). When we have enough numbers 330that any of the `bingos` is winning one, then we halt the reduction and return computed 331result. 332 333```elixir 334numbers 335|> Enum.reduce_while([], fn elem, acc -> 336 matches = [elem | acc] 337 338 case Enum.find(bingos, &Day4.win(&1, matches)) do 339 nil -> {:cont, matches} 340 bingo -> {:halt, Enum.sum(Day4.not_matched(bingo, matches)) * elem} 341 end 342end) 343``` 344 345```output 34634506 347``` 348 349### Task 2 350 351```elixir 352numbers 353|> Enum.reduce_while({bingos, []}, fn elem, {bingos, acc} -> 354 matches = [elem | acc] 355 356 case bingos do 357 [bingo] -> 358 if Day4.win(bingo, matches) do 359 {:halt, Enum.sum(Day4.not_matched(bingo, matches)) * elem} 360 else 361 {:cont, {bingos, matches}} 362 end 363 364 _ -> 365 {:cont, {Enum.reject(bingos, &Day4.win(&1, matches)), matches}} 366 end 367end) 368``` 369 370```output 3717686 372``` 373 374## Day 5 375 376```elixir 377defmodule Day5 do 378 defmodule Point do 379 defstruct [:x, :y] 380 381 def parse(input) do 382 [x, y] = String.split(input, ",") 383 384 %__MODULE__{x: String.to_integer(x), y: String.to_integer(y)} 385 end 386 end 387 388 defmodule Line do 389 defstruct [:start, :finish] 390 391 def new(a, b) do 392 {start, finish} = 393 cond do 394 a.x < b.x -> {a, b} 395 a.y < b.y -> {a, b} 396 true -> {b, a} 397 end 398 399 %__MODULE__{start: start, finish: finish} 400 end 401 402 def horizontal?(a), do: a.start.y == a.finish.y 403 def vertical?(a), do: a.start.x == a.finish.x 404 405 def points(a) do 406 case {sign(a.finish.x - a.start.x), sign(a.finish.y - a.start.y)} do 407 {0, dy} -> for y <- a.start.y..a.finish.y//dy, do: {a.start.x, y} 408 {dx, 0} -> for x <- a.start.x..a.finish.x//dx, do: {x, a.start.y} 409 {dx, dy} -> Enum.zip(a.start.x..a.finish.x//dx, a.start.y..a.finish.y//dy) 410 end 411 end 412 413 def orientation(a) do 414 cond do 415 horizontal?(a) -> :horizontal 416 vertical?(a) -> :vertical 417 true -> :diagonal 418 end 419 end 420 421 defp sign(0), do: 0 422 defp sign(x) when x < 0, do: -1 423 defp sign(x) when x > 0, do: 1 424 end 425end 426 427lines = 428 File.stream!("day5.txt") 429 |> Stream.map(&String.trim/1) 430 |> Stream.map(fn input -> 431 [a, b] = String.split(input, " -> ") 432 433 pa = Day5.Point.parse(a) 434 pb = Day5.Point.parse(b) 435 436 Day5.Line.new(pa, pb) 437 end) 438``` 439 440```output 441#Stream<[ 442 enum: %File.Stream{ 443 line_or_bytes: :line, 444 modes: [:raw, :read_ahead, :binary], 445 path: "day5.txt", 446 raw: true 447 }, 448 funs: [#Function<47.58486609/1 in Stream.map/2>, #Function<47.58486609/1 in Stream.map/2>] 449]> 450``` 451 452### Task 1 453 454```elixir 455lines 456|> Stream.filter(&(Day5.Line.orientation(&1) != :diagonal)) 457|> Stream.flat_map(&Day5.Line.points/1) 458|> Enum.frequencies() 459|> Enum.count(fn {_k, v} -> v > 1 end) 460``` 461 462```output 4635197 464``` 465 466### Task 2 467 468```elixir 469lines 470|> Stream.flat_map(&Day5.Line.points/1) 471|> Enum.frequencies() 472|> Enum.count(fn {_k, v} -> v > 1 end) 473``` 474 475```output 47618605 477``` 478 479## Day 6 480 481```elixir 482initial = for i <- 0..8, into: %{}, do: {i, 0} 483 484counts = 485 File.read!("day6.txt") 486 |> String.trim() 487 |> String.split(",") 488 |> Enum.map(&String.to_integer/1) 489 |> Enum.frequencies() 490 |> Map.merge(initial, fn _, a, _ -> a end) 491 492defmodule Day6 do 493 def next(%{0 => next} = population) do 494 1..8 495 |> Map.new(&{&1 - 1, population[&1]}) 496 |> Map.merge(%{6 => next, 8 => next}, fn _, v1, v2 -> v1 + v2 end) 497 end 498end 499``` 500 501```output 502{:module, Day6, <<70, 79, 82, 49, 0, 0, 7, ...>>, {:next, 1}} 503``` 504 505### Task 1 506 507```elixir 5081..80 509|> Enum.reduce(counts, fn _, acc -> Day6.next(acc) end) 510|> Map.values() 511|> Enum.sum() 512``` 513 514```output 515343441 516``` 517 518### Task 2 519 520```elixir 5211..256 522|> Enum.reduce(counts, fn _, acc -> Day6.next(acc) end) 523|> Map.values() 524|> Enum.sum() 525``` 526 527```output 5281569108373832 529``` 530 531## Day 7 532 533```elixir 534input = 535 File.read!("day7.txt") 536 |> String.trim() 537 |> String.split(",") 538 |> Enum.map(&String.to_integer/1) 539``` 540 541```output 542[1101, 1, 29, 67, 1102, 0, 1, 65, 1008, 65, 35, 66, 1005, 66, 28, 1, 67, 65, 20, 4, 0, 1001, 65, 1, 543 65, 1106, 0, 8, 99, 35, 67, 101, 99, 105, 32, 110, 39, 101, 115, 116, 32, 112, 97, 115, 32, 117, 544 110, 101, 32, 105, ...] 545``` 546 547### Task 1 548 549```elixir 550mean = Enum.at(Enum.sort(input), div(length(input), 2)) 551 552input 553|> Enum.map(&abs(&1 - mean)) 554|> Enum.sum() 555``` 556 557```output 558336721 559``` 560 561### Task 2 562 563```elixir 564arith_sum = fn n -> div(n * n + n, 2) end 565 566max = Enum.max(input) 567 5680..max 569|> Enum.reduce(:infinity, fn n, acc -> 570 sum = 571 input 572 |> Enum.map(&arith_sum.(abs(&1 - n))) 573 |> Enum.sum() 574 575 if sum < acc, do: sum, else: acc 576end) 577``` 578 579```output 58091638945 581``` 582 583## Day 8 584 585```elixir 586input = 587 File.stream!("day8.txt") 588 |> Stream.map(fn line -> 589 line 590 |> String.split(" | ") 591 |> Enum.map(fn part -> 592 part 593 |> String.trim() 594 |> String.split(" ") 595 |> Enum.map(fn disp -> 596 disp 597 |> String.to_charlist() 598 |> Enum.sort() 599 |> List.to_string() 600 end) 601 end) 602 |> List.to_tuple() 603 end) 604``` 605 606```output 607#Stream<[ 608 enum: %File.Stream{ 609 line_or_bytes: :line, 610 modes: [:raw, :read_ahead, :binary], 611 path: "day8.txt", 612 raw: true 613 }, 614 funs: [#Function<47.58486609/1 in Stream.map/2>] 615]> 616``` 617 618### Task 1 619 620We simply need to count all occurences of the values that have 2, 3, 4, or 7 highlighted 621segments. 622 623```elixir 624input 625|> Enum.map(fn {_, output} -> 626 Enum.count(output, &(byte_size(&1) in [2, 3, 4, 7])) 627end) 628|> Enum.sum() 629``` 630 631```output 632390 633``` 634 635### Task 2 636 637```elixir 638defmodule Day8.Task2 do 639 def a --- b do 640 MapSet.difference(a, b) 641 end 642 643 def a +++ b do 644 MapSet.union(a, b) 645 end 646 647 def a <~> b do 648 MapSet.intersection(a, b) 649 end 650 651 def a <|> b do 652 (a +++ b) --- (a <~> b) 653 end 654 655 # 1. 7. 4. 2|3|5. 2|3|5. 2|3|5. 6|9|0. 6|9|0. 6|9|0. 8. 656 def deduce({cf, acf, bcdf, acdeg, acdfg, abdfg, abdefg, abcdfg, abcefg, abcdefg}) do 657 a = acf --- cf 658 eg = abcdefg --- (acf +++ bcdf) 659 bd = bcdf --- cf 660 abfg = abdefg <|> abcdfg <|> abcefg 661 b = abfg <~> bd 662 f = abfg <~> cf 663 g = abfg --- (a +++ b +++ f) 664 d = bd --- b 665 c = cf --- f 666 e = eg --- g 667 668 {a, b, c, d, e, f, g} = 669 [a, b, c, d, e, f, g] 670 |> Enum.map(&extract/1) 671 |> List.to_tuple() 672 673 [ 674 # 0 675 [a, b, c, e, f, g], 676 # 1 677 [c, f], 678 # 2 679 [a, c, d, e, g], 680 # 3 681 [a, c, d, f, g], 682 # 4 683 [b, c, d, f], 684 # 5 685 [a, b, d, f, g], 686 # 6 687 [a, b, d, e, f, g], 688 # 7 689 [a, c, f], 690 # 8 691 [a, b, c, d, e, f, g], 692 # 9 693 [a, b, c, d, f, g] 694 ] 695 |> Enum.map(&List.to_string(Enum.sort(&1))) 696 |> Enum.with_index() 697 |> Map.new() 698 end 699 700 defp extract(a) do 701 [v] = MapSet.to_list(a) 702 703 v 704 end 705 706 def decode(matches, output) do 707 output 708 |> Enum.map(&matches[&1]) 709 |> Integer.undigits() 710 end 711end 712 713input 714|> Enum.map(fn {input, output} -> 715 input 716 |> Enum.sort_by(&byte_size/1) 717 |> Enum.map(&MapSet.new(String.to_charlist(&1))) 718 |> List.to_tuple() 719 |> Day8.Task2.deduce() 720 |> Day8.Task2.decode(output) 721end) 722|> Enum.sum() 723``` 724 725```output 726warning: variable "abdfg" is unused (if the variable is not meant to be used, prefix it with an underscore) 727 solutions.livemd#cell:19: Day8.Task2.deduce/1 728 729warning: variable "acdeg" is unused (if the variable is not meant to be used, prefix it with an underscore) 730 solutions.livemd#cell:19: Day8.Task2.deduce/1 731 732warning: variable "acdfg" is unused (if the variable is not meant to be used, prefix it with an underscore) 733 solutions.livemd#cell:19: Day8.Task2.deduce/1 734 735``` 736 737```output 7381011785 739```