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 {:nx, github: "elixir-nx/nx", sparse: "nx"}, 15 {:kino, github: "livebook-dev/kino"} 16]) 17``` 18 19```output 20* Getting nx (https://github.com/elixir-nx/nx.git) 21remote: Enumerating objects: 10786, done. 22remote: Counting objects: 100% (2841/2841), done. 23remote: Compressing objects: 100% (635/635), done. 24remote: Total 10786 (delta 2376), reused 2515 (delta 2195), pack-reused 7945 25origin/HEAD set to main 26* Getting kino (https://github.com/livebook-dev/kino.git) 27remote: Enumerating objects: 488, done. 28remote: Counting objects: 100% (488/488), done. 29remote: Compressing objects: 100% (351/351), done. 30remote: Total 488 (delta 269), reused 274 (delta 118), pack-reused 0 31origin/HEAD set to main 32==> kino 33Compiling 19 files (.ex) 34Generated kino app 35==> nx 36Compiling 23 files (.ex) 37Generated nx app 38``` 39 40```output 41:ok 42``` 43 44## Day 1 45 46### Load input 47 48```elixir 49stream = 50 File.stream!("day1.txt") 51 |> Stream.map(&String.to_integer(String.trim(&1))) 52``` 53 54```output 55#Stream<[ 56 enum: %File.Stream{ 57 line_or_bytes: :line, 58 modes: [:raw, :read_ahead, :binary], 59 path: "day1.txt", 60 raw: true 61 }, 62 funs: [#Function<47.58486609/1 in Stream.map/2>] 63]> 64``` 65 66### Task 1 67 68<!-- livebook:{"break_markdown":true} --> 69 70Compute count of consecutive increases 71 72```elixir 73stream 74|> Stream.chunk_every(2, 1, :discard) 75|> Enum.count(fn [a, b] -> a < b end) 76``` 77 78```output 791688 80``` 81 82### Task 2 83 84<!-- livebook:{"break_markdown":true} --> 85 86Compute count of consecutive increases of sums of trigrams. 87 88However we can notice, that if we have list like: 89 90$$ 91[a, b, c, d] 92$$ 93 94Then when we want to compare consecutive trigrams then we compare: 95 96$$ 97a + b + c < b + c + d \\ 98a < d 99$$ 100 101So we can traverse each 4 elements and then just compare first and last one 102instead of summing and then traversing it again. 103 104```elixir 105stream 106|> Stream.chunk_every(4, 1, :discard) 107|> Enum.count(fn [a, _, _, b] -> a < b end) 108``` 109 110```output 1111728 112``` 113 114## Day 2 115 116### Load input 117 118We do parsing there, as it will help us with the latter tasks. Pattern matching 119is the simplest approach there, as input is in form of: 120 121``` 122forward 10 123up 20 124down 30 125``` 126 127We need to `trim/1` input to make sure that the last newline will not interrupt 128`String.to_integer/1` calls. 129 130```elixir 131stream = 132 File.stream!("day2.txt") 133 |> Stream.map(fn input -> 134 case String.trim(input) do 135 "forward " <> n -> {:forward, String.to_integer(n)} 136 "up " <> n -> {:up, String.to_integer(n)} 137 "down " <> n -> {:down, String.to_integer(n)} 138 end 139 end) 140``` 141 142```output 143#Stream<[ 144 enum: %File.Stream{ 145 line_or_bytes: :line, 146 modes: [:raw, :read_ahead, :binary], 147 path: "day2.txt", 148 raw: true 149 }, 150 funs: [#Function<47.58486609/1 in Stream.map/2>] 151]> 152``` 153 154### Task 1 155 156```elixir 157{h, d} = 158 stream 159 |> Enum.reduce({0, 0}, fn 160 {:forward, n}, {h, d} -> {h + n, d} 161 {:up, n}, {h, d} -> {h, d - n} 162 {:down, n}, {h, d} -> {h, d + n} 163 end) 164 165h * d 166``` 167 168```output 1691499229 170``` 171 172### Task 2 173 174```elixir 175{h, d, _} = 176 stream 177 |> Enum.reduce({0, 0, 0}, fn 178 {:forward, n}, {h, d, a} -> {h + n, d + a * n, a} 179 {:up, n}, {h, d, a} -> {h, d, a - n} 180 {:down, n}, {h, d, a} -> {h, d, a + n} 181 end) 182 183h * d 184``` 185 186```output 1871340836560 188``` 189 190## Day 3 191 192### Input 193 194```elixir 195stream = 196 File.stream!("day3.txt") 197 |> Enum.map(&String.trim/1) 198 |> Enum.map(&String.to_charlist/1) 199 200defmodule Day3 do 201 def count(list) do 202 Enum.reduce(list, List.duplicate(0, 12), fn input, acc -> 203 for {value, counter} <- Enum.zip(input, acc) do 204 case value do 205 ?1 -> counter + 1 206 ?0 -> counter 207 end 208 end 209 end) 210 end 211end 212``` 213 214```output 215{:module, Day3, <<70, 79, 82, 49, 0, 0, 7, ...>>, {:count, 1}} 216``` 217 218### Task 1 219 220```elixir 221half = div(length(stream), 2) 222 223{a, b} = 224 stream 225 |> Day3.count() 226 |> Enum.reduce({0, 0}, fn elem, {a, b} -> 227 if elem > half do 228 {a * 2 + 1, b * 2} 229 else 230 {a * 2, b * 2 + 1} 231 end 232 end) 233 234a * b 235``` 236 237```output 2383847100 239``` 240 241### Task 2 242 243```elixir 244defmodule Day3.Task2 do 245 def reduce(list, cb), do: reduce(list, 0, cb) 246 247 defp reduce([elem], _, _), do: elem 248 249 defp reduce(list, at, cb) do 250 counts = Day3.count(list) 251 252 half = div(length(list), 2) 253 count = Enum.at(counts, at) 254 255 bit = 256 cond do 257 count == half and cb.(count + 1, half) -> ?1 258 count != half and cb.(count, half) -> ?1 259 true -> ?0 260 end 261 262 reduce(Enum.filter(list, &(Enum.at(&1, at) == bit)), at + 1, cb) 263 end 264end 265 266co2 = List.to_integer(Day3.Task2.reduce(stream, &</2), 2) 267o2 = List.to_integer(Day3.Task2.reduce(stream, &>/2), 2) 268 269co2 * o2 270``` 271 272```output 2734105235 274``` 275 276## Day 4 277 278### Input 279 280This time it is a little bit more convoluted, as there are 2 parts of the input. 281Fortunately we can easily disect the parts via pattern matching. 282 283Technically the conversion to the numbers is not needed, but it does no harm 284and provides additional layer of safety against some whitespace characters left there 285and here. 286 287The `Day4.win/2` function is manually unrolled, as it is easier to write than some 288random jumping in the list. 289 290<!-- livebook:{"disable_formatting":true} --> 291 292```elixir 293[numbers | bingos] = 294 File.read!("day4.txt") 295 |> String.split("\n\n", trim: true) 296 297numbers = 298 numbers 299 |> String.trim() 300 |> String.split(",") 301 |> Enum.map(&String.to_integer/1) 302 303bingos = 304 bingos 305 |> Enum.map(fn bingo -> 306 bingo 307 |> String.split(~r/\s+/, trim: true) 308 |> Enum.map(&String.to_integer/1) 309 end) 310 311defmodule Day4 do 312 def win( 313 [ 314 a1, a2, a3, a4, a5, 315 b1, b2, b3, b4, b5, 316 c1, c2, c3, c4, c5, 317 d1, d2, d3, d4, d5, 318 e1, e2, e3, e4, e5 319 ], 320 nums 321 ) do 322 # Rows 323 all_in([a1, a2, a3, a4, a5], nums) or 324 all_in([b1, b3, b3, b4, b5], nums) or 325 all_in([c1, c2, c3, c4, c5], nums) or 326 all_in([d1, d2, d3, d4, d5], nums) or 327 all_in([e1, e2, e3, e4, e5], nums) or 328 # Columns 329 all_in([a1, b1, c1, d1, e1], nums) or 330 all_in([a2, b2, c2, d2, e2], nums) or 331 all_in([a3, b3, c3, d3, e3], nums) or 332 all_in([a4, b4, c4, d4, e4], nums) or 333 all_in([a5, b5, c5, d5, e5], nums) 334 end 335 336 def not_matched(bingo, nums) do 337 Enum.reject(bingo, &(&1 in nums)) 338 end 339 340 defp all_in(list, nums) do 341 Enum.all?(list, &(&1 in nums)) 342 end 343end 344``` 345 346```output 347{:module, Day4, <<70, 79, 82, 49, 0, 0, 15, ...>>, {:all_in, 2}} 348``` 349 350### Task 1 351 352We simply traverse the `numbers` list aggregating the numbers (order doesn't really matter, 353here we aggregate them in reverse order to speedup the code). When we have enough numbers 354that any of the `bingos` is winning one, then we halt the reduction and return computed 355result. 356 357```elixir 358numbers 359|> Enum.reduce_while([], fn elem, acc -> 360 matches = [elem | acc] 361 362 case Enum.find(bingos, &Day4.win(&1, matches)) do 363 nil -> {:cont, matches} 364 bingo -> {:halt, Enum.sum(Day4.not_matched(bingo, matches)) * elem} 365 end 366end) 367``` 368 369```output 37034506 371``` 372 373### Task 2 374 375```elixir 376numbers 377|> Enum.reduce_while({bingos, []}, fn elem, {bingos, acc} -> 378 matches = [elem | acc] 379 380 case bingos do 381 [bingo] -> 382 if Day4.win(bingo, matches) do 383 {:halt, Enum.sum(Day4.not_matched(bingo, matches)) * elem} 384 else 385 {:cont, {bingos, matches}} 386 end 387 388 _ -> 389 {:cont, {Enum.reject(bingos, &Day4.win(&1, matches)), matches}} 390 end 391end) 392``` 393 394```output 3957686 396``` 397 398## Day 5 399 400```elixir 401defmodule Day5 do 402 defmodule Point do 403 defstruct [:x, :y] 404 405 def parse(input) do 406 [x, y] = String.split(input, ",") 407 408 %__MODULE__{x: String.to_integer(x), y: String.to_integer(y)} 409 end 410 end 411 412 defmodule Line do 413 defstruct [:start, :finish] 414 415 def new(a, b) do 416 {start, finish} = 417 cond do 418 a.x < b.x -> {a, b} 419 a.y < b.y -> {a, b} 420 true -> {b, a} 421 end 422 423 %__MODULE__{start: start, finish: finish} 424 end 425 426 def horizontal?(a), do: a.start.y == a.finish.y 427 def vertical?(a), do: a.start.x == a.finish.x 428 429 def points(a) do 430 case {sign(a.finish.x - a.start.x), sign(a.finish.y - a.start.y)} do 431 {0, dy} -> for y <- a.start.y..a.finish.y//dy, do: {a.start.x, y} 432 {dx, 0} -> for x <- a.start.x..a.finish.x//dx, do: {x, a.start.y} 433 {dx, dy} -> Enum.zip(a.start.x..a.finish.x//dx, a.start.y..a.finish.y//dy) 434 end 435 end 436 437 def orientation(a) do 438 cond do 439 horizontal?(a) -> :horizontal 440 vertical?(a) -> :vertical 441 true -> :diagonal 442 end 443 end 444 445 defp sign(0), do: 0 446 defp sign(x) when x < 0, do: -1 447 defp sign(x) when x > 0, do: 1 448 end 449end 450 451lines = 452 File.stream!("day5.txt") 453 |> Stream.map(&String.trim/1) 454 |> Stream.map(fn input -> 455 [a, b] = String.split(input, " -> ") 456 457 pa = Day5.Point.parse(a) 458 pb = Day5.Point.parse(b) 459 460 Day5.Line.new(pa, pb) 461 end) 462``` 463 464```output 465#Stream<[ 466 enum: %File.Stream{ 467 line_or_bytes: :line, 468 modes: [:raw, :read_ahead, :binary], 469 path: "day5.txt", 470 raw: true 471 }, 472 funs: [#Function<47.58486609/1 in Stream.map/2>, #Function<47.58486609/1 in Stream.map/2>] 473]> 474``` 475 476### Task 1 477 478```elixir 479lines 480|> Stream.filter(&(Day5.Line.orientation(&1) != :diagonal)) 481|> Stream.flat_map(&Day5.Line.points/1) 482|> Enum.frequencies() 483|> Enum.count(fn {_k, v} -> v > 1 end) 484``` 485 486```output 4875197 488``` 489 490### Task 2 491 492```elixir 493lines 494|> Stream.flat_map(&Day5.Line.points/1) 495|> Enum.frequencies() 496|> Enum.count(fn {_k, v} -> v > 1 end) 497``` 498 499```output 50018605 501``` 502 503## Day 6 504 505```elixir 506initial = for i <- 0..8, into: %{}, do: {i, 0} 507 508counts = 509 File.read!("day6.txt") 510 |> String.trim() 511 |> String.split(",") 512 |> Enum.map(&String.to_integer/1) 513 |> Enum.frequencies() 514 |> Map.merge(initial, fn _, a, _ -> a end) 515 516defmodule Day6 do 517 def next(%{0 => next} = population) do 518 1..8 519 |> Map.new(&{&1 - 1, population[&1]}) 520 |> Map.merge(%{6 => next, 8 => next}, fn _, v1, v2 -> v1 + v2 end) 521 end 522end 523``` 524 525```output 526{:module, Day6, <<70, 79, 82, 49, 0, 0, 7, ...>>, {:next, 1}} 527``` 528 529### Task 1 530 531```elixir 5321..80 533|> Enum.reduce(counts, fn _, acc -> Day6.next(acc) end) 534|> Map.values() 535|> Enum.sum() 536``` 537 538```output 539343441 540``` 541 542### Task 2 543 544```elixir 5451..256 546|> Enum.reduce(counts, fn _, acc -> Day6.next(acc) end) 547|> Map.values() 548|> Enum.sum() 549``` 550 551```output 5521569108373832 553``` 554 555## Day 7 556 557```elixir 558input = 559 File.read!("day7.txt") 560 |> String.trim() 561 |> String.split(",") 562 |> Enum.map(&String.to_integer/1) 563``` 564 565```output 566[1101, 1, 29, 67, 1102, 0, 1, 65, 1008, 65, 35, 66, 1005, 66, 28, 1, 67, 65, 20, 4, 0, 1001, 65, 1, 567 65, 1106, 0, 8, 99, 35, 67, 101, 99, 105, 32, 110, 39, 101, 115, 116, 32, 112, 97, 115, 32, 117, 568 110, 101, 32, 105, ...] 569``` 570 571### Task 1 572 573```elixir 574mean = Enum.at(Enum.sort(input), div(length(input), 2)) 575 576input 577|> Enum.map(&abs(&1 - mean)) 578|> Enum.sum() 579``` 580 581```output 582336721 583``` 584 585### Task 2 586 587```elixir 588arith_sum = fn n -> div(n * n + n, 2) end 589 590max = Enum.max(input) 591 592mean = Enum.sum(input) / length(input) 593 594[floor(mean), ceil(mean)] 595|> Enum.map(fn n -> 596 input 597 |> Enum.map(&arith_sum.(abs(&1 - n))) 598 |> Enum.sum() 599end) 600|> Enum.min() 601``` 602 603```output 60491638945 605``` 606 607## Day 8 608 609```elixir 610input = 611 File.stream!("day8.txt") 612 |> Stream.map(fn line -> 613 line 614 |> String.split(" | ") 615 |> Enum.map(fn part -> 616 part 617 |> String.trim() 618 |> String.split(" ") 619 |> Enum.map(fn disp -> 620 # Sort characters in each entry to simplify later work 621 disp 622 |> String.to_charlist() 623 |> Enum.sort() 624 |> List.to_string() 625 end) 626 end) 627 |> List.to_tuple() 628 end) 629``` 630 631```output 632#Stream<[ 633 enum: %File.Stream{ 634 line_or_bytes: :line, 635 modes: [:raw, :read_ahead, :binary], 636 path: "day8.txt", 637 raw: true 638 }, 639 funs: [#Function<47.58486609/1 in Stream.map/2>] 640]> 641``` 642 643### Task 1 644 645We simply need to count all occurences of the values that have 2, 3, 4, or 7 highlighted 646segments. 647 648```elixir 649input 650|> Enum.map(fn {_, output} -> 651 Enum.count(output, &(byte_size(&1) in [2, 3, 4, 7])) 652end) 653|> Enum.sum() 654``` 655 656```output 657390 658``` 659 660### Task 2 661 662```elixir 663defmodule Day8.Task2 do 664 defp a --- b, do: MapSet.difference(a, b) 665 666 defp a +++ b, do: MapSet.union(a, b) 667 668 defp a <~> b, do: MapSet.intersection(a, b) 669 670 # 1. 7. 4. 2|3|5. 2|3|5. 2|3|5. 6|9|0. 6|9|0. 6|9|0. 8. 671 def deduce([cf, acf, bcdf, acdeg, acdfg, abdfg, abdefg, abcdfg, abcefg, abcdefg]) do 672 eg = abcdefg --- (acf +++ bcdf) 673 bd = bcdf --- cf 674 adg = acdeg <~> acdfg <~> abdfg 675 abfg = abdefg <~> abcdfg <~> abcefg 676 a = acf --- cf 677 b = abfg <~> bd 678 f = abfg <~> cf 679 g = adg <~> eg 680 d = adg <~> bd 681 c = cf --- f 682 e = eg --- g 683 684 [a, b, c, d, e, f, g] = 685 [a, b, c, d, e, f, g] 686 |> Enum.map(&extract/1) 687 688 [ 689 # 0 690 [a, b, c, e, f, g], 691 # 1 692 [c, f], 693 # 2 694 [a, c, d, e, g], 695 # 3 696 [a, c, d, f, g], 697 # 4 698 [b, c, d, f], 699 # 5 700 [a, b, d, f, g], 701 # 6 702 [a, b, d, e, f, g], 703 # 7 704 [a, c, f], 705 # 8 706 [a, b, c, d, e, f, g], 707 # 9 708 [a, b, c, d, f, g] 709 ] 710 |> Enum.map(&List.to_string(Enum.sort(&1))) 711 |> Enum.with_index() 712 |> Map.new() 713 end 714 715 defp extract(a) do 716 # Just additional sanity check 717 [v] = MapSet.to_list(a) 718 719 v 720 end 721 722 def decode(matches, output) do 723 output 724 |> Enum.map(&matches[&1]) 725 |> Integer.undigits() 726 end 727end 728 729input 730|> Enum.map(fn {input, output} -> 731 input 732 |> Enum.sort_by(&byte_size/1) 733 |> Enum.map(&MapSet.new(String.to_charlist(&1))) 734 |> Day8.Task2.deduce() 735 |> Day8.Task2.decode(output) 736end) 737|> Enum.sum() 738``` 739 740```output 7411011785 742``` 743 744## Day 9 745 746```elixir 747input = 748 File.read!("day9.txt") 749 # ~S[110 750 # 101 751 # 110] 752 |> String.split("\n", trim: true) 753 |> Enum.map(&String.to_charlist(String.trim(&1))) 754 |> Nx.tensor(names: [:y, :x]) 755 |> Nx.subtract(?0) 756 |> Nx.add(1) 757 758{width, height} = shape = Nx.shape(input) 759``` 760 761```output 762{100, 100} 763``` 764 765### Task 1 766 767```elixir 768padded = Nx.pad(input, 99, [{0, 0, 0}, {1, 1, 0}]) 769 770shifted = Nx.slice_axis(padded, 0, height, :x) 771 772x1 = Nx.less(input, shifted) 773 774shifted = Nx.slice_axis(padded, 2, height, :x) 775 776x2 = Nx.less(input, shifted) 777 778x = Nx.logical_and(x1, x2) 779 780padded = Nx.pad(input, 99, [{1, 1, 0}, {0, 0, 0}]) 781 782shifted = Nx.slice_axis(padded, 0, width, :y) 783 784y1 = Nx.less(input, shifted) 785 786shifted = Nx.slice_axis(padded, 2, width, :y) 787 788y2 = Nx.less(input, shifted) 789 790y = Nx.logical_and(y1, y2) 791 792minimas = Nx.logical_and(x, y) 793 794input 795|> Nx.multiply(minimas) 796|> Nx.sum() 797|> Nx.to_number() 798``` 799 800```output 801452 802``` 803 804### Task 2 805 806```elixir 807input 808|> Nx.equal(10) 809|> Nx.logical_not() 810|> Nx.select(Nx.iota(shape), 9999) 811|> Nx.to_flat_list() 812|> Enum.reject(&(&1 == 9999)) 813|> Enum.map(fn point -> {div(point, width), rem(point, width)} end) 814|> Enum.reduce([], fn {y, x} = point, basins -> 815 basin_left = Enum.find_index(basins, &({y, x - 1} in &1)) 816 basin_up = Enum.find_index(basins, &({y - 1, x} in &1)) 817 818 case {basin_left, basin_up} do 819 {nil, nil} -> 820 [MapSet.new([point]) | basins] 821 822 {idx, nil} -> 823 List.update_at(basins, idx, &MapSet.put(&1, point)) 824 825 {nil, idx} -> 826 List.update_at(basins, idx, &MapSet.put(&1, point)) 827 828 {idx, idx} -> 829 List.update_at(basins, idx, &MapSet.put(&1, point)) 830 831 {idx1, idx2} -> 832 {old, basins} = List.pop_at(basins, max(idx1, idx2)) 833 834 List.update_at(basins, min(idx1, idx2), &(&1 |> MapSet.union(old) |> MapSet.put(point))) 835 end 836end) 837|> Enum.map(&MapSet.size/1) 838|> Enum.sort(:desc) 839|> Enum.take(3) 840|> Enum.reduce(&*/2) 841``` 842 843```output 8441263735 845```