Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Advent of Code 2022

notebook.livemd

Advent of Code 2022

Mix.install([
  {:memoize, "~> 1.4"},
  {:tesla, "~> 1.4"},
  {:hackney, "~> 1.18"}
])

defmodule Api do
  use Tesla
  use Memoize

  @session System.get_env("AOC_SESSION")

  adapter(Tesla.Adapter.Hackney)

  plug(Tesla.Middleware.BaseUrl, "https://adventofcode.com/2022/day/")
  plug(Tesla.Middleware.Headers, [{"cookie", "session=#{@session}"}])

  defmemo get_input(day) do
    {:ok, response} = get("#{day}/input")
    response.body
  end
end

:ok

Task 1

test_input = """
1000
2000
3000

4000

5000
6000

7000
8000
9000

10000
"""

solve1 = fn input ->
  input
  |> String.split("\n\n")
  |> Enum.map(fn cals ->
    cals |> String.split("\n", trim: true) |> Enum.map(&String.to_integer/1) |> Enum.sum()
  end)
  |> Enum.max()
end

24000 = solve1.(test_input)
solve1.(Api.get_input(1)) |> IO.inspect(label: "1.1")

solve2 = fn input ->
  input
  |> String.split("\n\n")
  |> Enum.map(fn cals ->
    cals |> String.split("\n", trim: true) |> Enum.map(&String.to_integer/1) |> Enum.sum()
  end)
  |> Enum.sort(&>=/2)
  |> Enum.take(3)
  |> Enum.sum()
end

45000 = solve2.(test_input)
solve2.(Api.get_input(1)) |> IO.inspect(label: "1.2")

:ok

Task 2

test_input = """
A Y
B X
C Z
"""

solve1 = fn input ->
  input
  |> String.split("\n", trim: true)
  |> Enum.map(fn round ->
    String.split(round, " ")
    |> case do
      ["A", "X"] -> 1 + 3
      ["A", "Y"] -> 2 + 6
      ["A", "Z"] -> 3 + 0
      ["B", "X"] -> 1 + 0
      ["B", "Y"] -> 2 + 3
      ["B", "Z"] -> 3 + 6
      ["C", "X"] -> 1 + 6
      ["C", "Y"] -> 2 + 0
      ["C", "Z"] -> 3 + 3
    end
  end)
  |> Enum.sum()
end

15 = solve1.(test_input)
solve1.(Api.get_input(2)) |> IO.inspect(label: "2.1")

solve2 = fn input ->
  input
  |> String.split("\n", trim: true)
  |> Enum.map(fn round ->
    String.split(round, " ")
    |> case do
      ["A", "X"] -> 3 + 0
      ["A", "Y"] -> 1 + 3
      ["A", "Z"] -> 2 + 6
      ["B", "X"] -> 1 + 0
      ["B", "Y"] -> 2 + 3
      ["B", "Z"] -> 3 + 6
      ["C", "X"] -> 2 + 0
      ["C", "Y"] -> 3 + 3
      ["C", "Z"] -> 1 + 6
    end
  end)
  |> Enum.sum()
end

12 = solve2.(test_input)
solve2.(Api.get_input(2)) |> IO.inspect(label: "2.2")

:ok

Task 3

test_input = """
vJrwpWtwJgWrhcsFMMfFFhFp
jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL
PmmdzqPrVvPwwTWBwg
wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn
ttgJtRGJQctTZtZT
CrZsJsPPZsGzwwsLwLmpwMDw
"""

priorities =
  [?a..?z, ?A..?Z]
  |> Enum.flat_map(fn chars -> Enum.map(chars, &amp;<<&amp;1::utf8>>) end)
  |> Enum.with_index(1)
  |> Enum.into(%{})

solve1 = fn input ->
  input
  |> String.split("\n", trim: true)
  |> Enum.map(fn rucksack ->
    with [first_half, second_half] <-
           rucksack
           |> String.split_at(div(String.length(rucksack), 2))
           |> Tuple.to_list()
           |> Enum.map(&amp;(&amp;1 |> String.split("", trim: true) |> MapSet.new())),
         [common] <- MapSet.intersection(first_half, second_half) |> MapSet.to_list() do
      Map.get(priorities, common)
    end
  end)
  |> Enum.sum()
end

157 = solve1.(test_input)
solve1.(Api.get_input(3)) |> IO.inspect(label: "3.1")

solve2 = fn input ->
  input
  |> String.split("\n", trim: true)
  |> Enum.chunk_every(3)
  |> Enum.map(fn rucksacks ->
    with [first, second, third] <-
           Enum.map(rucksacks, &amp;(&amp;1 |> String.split("", trim: true) |> MapSet.new())),
         [common] <-
           first |> MapSet.intersection(second) |> MapSet.intersection(third) |> MapSet.to_list() do
      Map.get(priorities, common)
    end
  end)
  |> Enum.sum()
end

70 = solve2.(test_input)
solve2.(Api.get_input(3)) |> IO.inspect(label: "3.2")

:ok

Task 4

test_input = """
2-4,6-8
2-3,4-5
5-7,7-9
2-8,3-7
6-6,4-6
2-6,4-8
"""

solve1 = fn input ->
  input
  |> String.split("\n", trim: true)
  |> Enum.map(fn pair_assignments ->
    pair_assignments
    |> String.split(",")
    |> Enum.map(fn elf ->
      elf
      |> String.split("-")
      |> Enum.map(&amp;String.to_integer/1)
      |> then(fn [first, last] -> Range.new(first, last) |> Enum.to_list() |> MapSet.new() end)
    end)
    |> then(fn [first_elf_assignments, second_elf_assignments] ->
      MapSet.subset?(first_elf_assignments, second_elf_assignments) or
        MapSet.subset?(second_elf_assignments, first_elf_assignments)
    end)
  end)
  |> Enum.count(&amp; &amp;1)
end

2 = solve1.(test_input)
solve1.(Api.get_input(4)) |> IO.inspect(label: "4.1")

solve2 = fn input ->
  input
  |> String.split("\n", trim: true)
  |> Enum.map(fn pair_assignments ->
    pair_assignments
    |> String.split(",")
    |> Enum.map(fn elf ->
      elf
      |> String.split("-")
      |> Enum.map(&amp;String.to_integer/1)
      |> then(fn [first, last] -> Range.new(first, last) end)
    end)
    |> then(fn [first_elf_assignments, second_elf_assignments] ->
      Range.disjoint?(first_elf_assignments, second_elf_assignments)
    end)
  end)
  |> Enum.count(&amp;(!&amp;1))
end

4 = solve2.(test_input)
solve2.(Api.get_input(4)) |> IO.inspect(label: "4.2")

:ok

Task 5

test_input = """
    [D]    
[N] [C]    
[Z] [M] [P]
 1   2   3 

move 1 from 2 to 1
move 3 from 1 to 3
move 2 from 2 to 1
move 1 from 1 to 2
"""

parse_input = fn input ->
  with [stacks_input, moves_input] <- input |> String.split("\n\n", trim: true),
       stacks <-
         stacks_input
         |> String.split("\n", trim: true)
         |> Enum.flat_map(
           &amp;(&amp;1
             |> String.split("", trim: true)
             |> Enum.with_index()
             |> Enum.filter(fn {c, _} -> c =~ ~r/[A-Z]/ end))
         )
         |> Enum.group_by(fn {_, i} -> i end, fn {c, _} -> c end)
         |> Map.values()
         |> Enum.map(&amp;Enum.reverse/1)
         |> Enum.with_index(1)
         |> Enum.into(%{}, fn {stack, index} -> {index, stack} end),
       moves <-
         moves_input
         |> String.split("\n", trim: true)
         |> Enum.map(
           &amp;(~r/\d+/
             |> Regex.scan(&amp;1)
             |> Enum.map(fn [v] -> String.to_integer(v) end))
         ) do
    {stacks, moves}
  end
end

solve1 = fn input ->
  {input_stacks, moves} = parse_input.(input)

  moves
  |> Enum.reduce(input_stacks, fn [count, from, to], stacks ->
    {crates_left, crates_to_move} = Enum.split(stacks[from], -count)

    stacks
    |> Map.put(from, crates_left)
    |> Map.update!(to, &amp;(&amp;1 ++ Enum.reverse(crates_to_move)))
  end)
  |> Map.values()
  |> Enum.map(&amp;List.last/1)
  |> Enum.join()
end

"CMZ" = solve1.(test_input)
solve1.(Api.get_input(5)) |> IO.inspect(label: "5.1")

solve2 = fn input ->
  {input_stacks, moves} = parse_input.(input)

  moves
  |> Enum.reduce(input_stacks, fn [count, from, to], stack ->
    {crates_left, crates_to_move} = Enum.split(stack[from], -count)

    stack
    |> Map.put(from, crates_left)
    |> Map.update!(to, &amp;(&amp;1 ++ crates_to_move))
  end)
  |> Map.values()
  |> Enum.map(&amp;List.last/1)
  |> Enum.join()
end

"MCD" = solve2.(test_input)
solve2.(Api.get_input(5)) |> IO.inspect(label: "5.2")

:ok

Task 6

test_inputs = [
  "mjqjpqmgbljsphdztnvjfqwrcgsmlb",
  "bvwbjplbgvbhsrlpgdmjqwftvncz",
  "nppdvjthqldpwncqszvftbrmjlhg",
  "nznrnfrfntjfmvfwmzdfjlvtqnbhcprsg",
  "zcfzfwzzqfrljwzlrfnpqdbhtmscgvjw"
]

solve = fn input, marker_length ->
  input
  |> String.to_charlist()
  |> Stream.chunk_every(marker_length, 1)
  |> Stream.take_while(&amp;(&amp;1 |> Enum.uniq() |> length() != marker_length))
  |> Enum.count()
  |> Kernel.+(marker_length)
end

[7, 5, 6, 10, 11] = Enum.map(test_inputs, &amp;solve.(&amp;1, 4))
solve.(Api.get_input(6), 4) |> IO.inspect(label: "6.1")

[19, 23, 23, 29, 26] = Enum.map(test_inputs, &amp;solve.(&amp;1, 14))
solve.(Api.get_input(6), 14) |> IO.inspect(label: "6.2")

Task 7

test_input = """
$ cd /
$ ls
dir a
14848514 b.txt
8504156 c.dat
dir d
$ cd a
$ ls
dir e
29116 f
2557 g
62596 h.lst
$ cd e
$ ls
584 i
$ cd ..
$ cd ..
$ cd d
$ ls
4060174 j
8033020 d.log
5626152 d.ext
7214296 k
"""

solve1 = fn input ->
  input
  |> String.split("\n", trim: true)
  |> Enum.reduce({[], %{}}, fn
    "$ cd ..", {dir_stack, sizes} ->
      {tl(dir_stack), sizes}

    "$ cd " <> dir, {dir_stack, sizes} ->
      uuid = :crypto.strong_rand_bytes(32) |> Base.url_encode64() |> binary_part(0, 32)
      {["#{dir}::#{uuid}" | dir_stack], sizes}

    command, {dir_stack, sizes} ->
      command
      |> Integer.parse()
      |> case do
        {size, _} ->
          {dir_stack,
           dir_stack
           |> Enum.into(%{}, fn dir -> {dir, size} end)
           |> Map.merge(sizes, fn _k, v1, v2 -> v1 + v2 end)}

        _ ->
          {dir_stack, sizes}
      end
  end)
  |> elem(1)
  |> Enum.filter(fn {_, size} -> size <= 100_000 end)
  |> Enum.map(&amp;elem(&amp;1, 1))
  |> Enum.sum()
end

95437 = solve1.(test_input)
solve1.(Api.get_input(7)) |> IO.inspect(label: "7.1")

solve2 = fn input ->
  input
  |> String.split("\n", trim: true)
  |> Enum.reduce({[], %{}}, fn
    "$ cd ..", {dir_stack, sizes} ->
      {tl(dir_stack), sizes}

    "$ cd " <> dir, {dir_stack, sizes} ->
      uuid = :crypto.strong_rand_bytes(32) |> Base.url_encode64() |> binary_part(0, 32)
      {["#{dir}::#{uuid}" | dir_stack], sizes}

    command, {dir_stack, sizes} ->
      command
      |> Integer.parse()
      |> case do
        {size, _} ->
          {dir_stack,
           dir_stack
           |> Enum.into(%{}, fn dir -> {dir, size} end)
           |> Map.merge(sizes, fn _k, v1, v2 -> v1 + v2 end)}

        _ ->
          {dir_stack, sizes}
      end
  end)
  |> elem(1)
  |> Enum.sort_by(fn {_, size} -> -size end)
  |> then(fn [{_, all} | rest] ->
    rest
    |> Enum.reverse()
    |> Enum.find(fn {_, size} -> 70_000_000 - all + size > 30_000_000 end)
    |> elem(1)
  end)
end

24_933_642 = solve2.(test_input)
solve2.(Api.get_input(7)) |> IO.inspect(label: "7.2")

:ok

Task 8

test_input = """
30373
25512
65332
33549
35390
"""

solve1 = fn input ->
  with trees_rows <- String.split(input, "\n", trim: true),
       size <- length(trees_rows),
       trees <-
         trees_rows
         |> Enum.flat_map(fn row ->
           row |> String.split("", trim: true) |> Enum.map(&amp;String.to_integer/1)
         end)
         |> Enum.with_index() do
    trees
    |> Enum.map(fn {curr_height, curr_idx} ->
      row_idx = trunc(curr_idx / size)
      col_idx = rem(curr_idx, size)
      top = Enum.slice(trees, col_idx..curr_idx//size) |> Enum.reverse() |> tl
      left = Enum.slice(trees, (row_idx * size)..curr_idx) |> Enum.reverse() |> tl
      right = Enum.slice(trees, curr_idx..(row_idx * size + size - 1)) |> tl
      bottom = Enum.slice(trees, curr_idx..(size * size)//size) |> tl

      Enum.any?([top, left, right, bottom], fn trees_slice ->
        trees_slice == [] || Enum.all?(trees_slice, &amp;(elem(&amp;1, 0) < curr_height))
      end)
    end)
    |> Enum.count(&amp; &amp;1)
  end
end

21 = solve1.(test_input)
solve1.(Api.get_input(8)) |> IO.inspect(label: "8.1")

solve2 = fn input ->
  with trees_rows <- String.split(input, "\n", trim: true),
       size <- length(trees_rows),
       trees <-
         trees_rows
         |> Enum.flat_map(fn row ->
           row |> String.split("", trim: true) |> Enum.map(&amp;String.to_integer/1)
         end)
         |> Enum.with_index() do
    trees
    |> Enum.map(fn {curr_height, curr_idx} ->
      row_idx = trunc(curr_idx / size)
      col_idx = rem(curr_idx, size)
      top = Enum.slice(trees, col_idx..curr_idx//size) |> Enum.reverse() |> tl
      left = Enum.slice(trees, (row_idx * size)..curr_idx) |> Enum.reverse() |> tl
      right = Enum.slice(trees, curr_idx..(row_idx * size + size - 1)) |> tl
      bottom = Enum.slice(trees, curr_idx..(size * size)//size) |> tl

      Enum.map([top, left, right, bottom], fn trees_slice ->
        Enum.reduce_while(trees_slice, 0, fn
          {height, _}, score when height >= curr_height -> {:halt, score + 1}
          _, score -> {:cont, score + 1}
        end)
      end)
      |> Enum.product()
    end)
    |> Enum.max()
  end
end

8 = solve2.(test_input)
solve2.(Api.get_input(8)) |> IO.inspect(label: "8.2")

:ok

Task 9

defmodule Task9 do
  def solve1(input) do
    input
    |> parse_input
    |> Enum.reduce(
      [{0, 0}, {0, 0}, MapSet.new([{0, 0}])],
      fn step, [head, tail, tail_positions] ->
        new_head = move_head(head, step)
        new_tail = move_tail(new_head, tail, step)
        [new_head, new_tail, MapSet.put(tail_positions, new_tail)]
      end
    )
    |> then(fn [_, _, tail_positions] -> Enum.count(tail_positions) end)
  end

  defp parse_input(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.flat_map(
      &amp;(&amp;1
        |> String.split(" ")
        |> then(fn [direction, count] ->
          List.duplicate(direction, String.to_integer(count))
        end))
    )
  end

  defp move_head({x, y}, "R"), do: {x + 1, y}
  defp move_head({x, y}, "U"), do: {x, y + 1}
  defp move_head({x, y}, "L"), do: {x - 1, y}
  defp move_head({x, y}, "D"), do: {x, y - 1}

  defp move_tail({h_x, h_y} = _head, {t_x, t_y} = tail, step) do
    cond do
      t_x == h_x and t_y - h_y == 2 ->
        {t_x, t_y - 1}

      t_y == h_y and h_x - t_x == 2 ->
        {t_x + 1, t_y}

      t_x == h_x and h_y - t_y == 2 ->
        {t_x, t_y + 1}

      t_y == h_y and t_x - h_x == 2 ->
        {t_x - 1, t_y}

      t_y - h_y == 2 or h_x - t_x == 2 or h_y - t_y == 2 or t_x - h_x == 2 ->
        case step do
          "R" -> {h_x - 1, h_y}
          "U" -> {h_x, h_y - 1}
          "L" -> {h_x + 1, h_y}
          "D" -> {h_x, h_y + 1}
        end

      true ->
        tail
    end
  end
end

test_input = """
R 4
U 4
L 3
D 1
R 4
D 1
L 5
R 2
"""

13 = Task9.solve1(test_input)
Task9.solve1(Api.get_input(9)) |> IO.inspect(label: "9.1")

:ok