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

Advent of code 2024

2024/aoc.livemd

Advent of code 2024

Day 1

defmodule Day_1 do
  def input do
    """
    3   4
    4   3
    2   5
    1   3
    3   9
    3   3
    """

    File.read!("day1.txt")
  end

  def get_lists() do
    Day_1.input()
    |> String.split()
    |> Enum.map(&String.to_integer(&1))
    |> Enum.chunk_every(2)
    |> Enum.reduce({[], []}, fn [i, j], {acc_i, acc_j} ->
      {[i | acc_i], [j | acc_j]}
    end)
  end

  def run_part1() do
    {l1, l2} = get_lists()

    l1 = Enum.sort(l1)
    l2 = Enum.sort(l2)

    Enum.zip(l2, l1)
    |> Enum.map(fn
      {i, j} when i > j -> i - j
      {i, j} -> j - i
    end)
    |> Enum.sum()
  end

  def run_part2() do
    {l1, l2} = get_lists()
    fq = Enum.frequencies(l2)

    Enum.map(l1, fn l -> l * Map.get(fq, l, 0) end)
    |> Enum.sum()
  end
end
Day_1.run_part1()
Day_1.run_part2()

Day 2

defmodule Day_2 do
  def input() do
    """
    7 6 4 2 1
    1 2 7 8 9
    9 7 6 2 1
    1 3 2 4 5
    8 6 4 4 1
    1 3 6 7 9
    """

    File.read!("day2.txt")
  end

  def calculate(list, opts \\ []) do
    cond do
      Enum.sort(list, :asc) == list ->
        :increase

      Enum.sort(list, :desc) == list ->
        :decrease

      true ->
        :unknown
    end
    |> is_safe?(list, opts)
  end

  def get_level_differ(list, head) do
    Enum.reduce(list, {[], head}, fn l, {i, j} ->
      {i ++ [l - j], l}
    end)
    |> elem(0)
  end

  def is_safe?(_, _, opts \\ [])

  def is_safe?(:increase, [head | list], opts) do
    result =
      get_level_differ(list, head)
      |> Enum.all?(fn i -> i in [1, 2, 3] end)

    if Keyword.get(opts, :recheck, true) do
      result
      |> is_is_safe?([head | list])
    else
      result
    end
  end

  def is_safe?(:decrease, [head | list], opts) do
    result =
      get_level_differ(list, head)
      |> Enum.all?(fn i -> i in [-1, -2, -3] end)

    if Keyword.get(opts, :recheck, true) do
      result
      |> is_is_safe?([head | list])
    else
      result
    end
  end

  def is_safe?(_, list, opts) do
    if Keyword.get(opts, :recheck, true) do
      false
      |> is_is_safe?(list)
    else
      false
    end
  end

  def is_is_safe?(true, _) do
    true
  end

  def is_is_safe?(false, list) do
    list
    |> Enum.with_index()
    |> Enum.map(fn {_, index} ->
      List.delete_at(list, index)
      |> calculate(recheck: false)
    end)
    |> Enum.any?()
  end

  def run_part1() do
    Day_2.input()
    |> String.split("\n", trim: true)
    |> Enum.map(fn l ->
      String.split(l)
      |> Enum.map(&String.to_integer(&1))
      |> Day_2.calculate(recheck: false)
    end)
    |> Enum.count(fn r -> r == true end)
  end

  def run_part2() do
    Day_2.input()
    |> String.split("\n", trim: true)
    |> Enum.map(fn l ->
      String.split(l)
      |> Enum.map(&String.to_integer(&1))
      |> Day_2.calculate()
    end)
    |> Enum.count(fn r -> r == true end)
  end
end
Day_2.run_part1()
Day_2.run_part2()

Day 3

defmodule Day_3 do
  def input do
    "xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))"
    File.read!("day3.txt")
  end

  def input2 do
    "xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))"
  end

  def calculate(x) do
    Regex.named_captures(~r/mul\((?\d{1,3}),(?\d{1,3})\)/, x)
    |> Map.values()
    |> Enum.map(&String.to_integer(&1))
    |> Enum.product()
  end

  def run_part1() do
    Regex.scan(~r/mul\(\d{1,3},\d{1,3}\)/, Day_3.input())
    |> Enum.map(fn [x] -> calculate(x) end)
    |> Enum.sum()
  end

  def run_part2() do
    Regex.scan(~r/mul\(\d{1,3},\d{1,3}\)|do\(\)|don't\(\)/, Day_3.input())
    |> Enum.reduce({[], "do()"}, fn [i], {acc, x} ->
      cond do
        i == "do()" -> {acc, i}
        i == "don't()" -> {acc, i}
        x == "do()" -> {acc ++ [i], x}
        true -> {acc, x}
      end
    end)
    |> elem(0)
    |> Enum.map(fn x -> calculate(x) end)
    |> Enum.sum()
  end
end
Day_3.run_part1()
Day_3.run_part2()

Day 4

defmodule Day_4 do
  def input() do
    """
    MMMSXXMASM
    MSAMXMSMSA
    AMXSXMAAMM
    MSAMASMSMX
    XMASAMXAMM
    XXAMMXXAMA
    SMSMSASXSS
    SAXAMASAAA
    MAMMMXMMMM
    MXMXAXMASX
    """

    File.read!("day4.txt")
  end

  def get_word(str) do
    word = String.split(str, "", trim: true)
    word_reverse = Enum.reverse(word)
    {word, word_reverse, 0..(length(word) - 1)}
  end

  def get_grid() do
    Day_4.input()
    |> String.split("\n", trim: true)
    |> Enum.map(fn i -> String.split(i, "", trim: true) end)
  end

  def gen_diagonal(grid, i, j) do
    unless i < 0 || j < 0 do
      (Enum.at(grid, i) || [])
      |> Enum.at(j)
    end
  end

  @spec add_count(Integer.t(), boolean()) :: Integer.t()
  def add_count(count, res) do
    if res do
      count + 1
    else
      count
    end
  end

  def run_part1() do
    grid = Day_4.get_grid()

    {word, word_reverse, from_to} = Day_4.get_word("XMAS")

    Enum.with_index(grid)
    |> Enum.reduce(0, fn {y, i}, count ->
      Enum.with_index(y)
      |> Enum.reduce(count, fn {_x, j}, inner_count ->
        horizontal = Enum.map(from_to, fn g -> grid |> Enum.at(i) |> Enum.at(j + g) end)
        vertical = Enum.map(from_to, fn g -> (grid |> Enum.at(i + g) || []) |> Enum.at(j) end)
        diagonal_1 = Enum.map(from_to, &amp;Day_4.gen_diagonal(grid, i + &amp;1, j + &amp;1))
        diagonal_2 = Enum.map(from_to, &amp;Day_4.gen_diagonal(grid, i + &amp;1, j - &amp;1))

        inner_count
        |> Day_4.add_count(horizontal == word)
        |> Day_4.add_count(horizontal == word_reverse)
        |> Day_4.add_count(vertical == word)
        |> Day_4.add_count(vertical == word_reverse)
        |> Day_4.add_count(diagonal_1 == word)
        |> Day_4.add_count(diagonal_1 == word_reverse)
        |> Day_4.add_count(diagonal_2 == word)
        |> Day_4.add_count(diagonal_2 == word_reverse)
      end)
    end)
  end

  def run_part2() do
    grid = Day_4.get_grid()

    {word, word_reverse, from_to} = Day_4.get_word("MAS")

    Enum.with_index(grid)
    |> Enum.reduce(0, fn {y, i}, count ->
      Enum.with_index(y)
      |> Enum.reduce(count, fn {_x, j}, inner_count ->
        diagonal_1 = Enum.map(from_to, &amp;Day_4.gen_diagonal(grid, i + &amp;1, j + &amp;1))
        diagonal_2 = Enum.map(from_to, &amp;Day_4.gen_diagonal(grid, i + &amp;1, j - &amp;1 + 2))

        inner_count
        |> Day_4.add_count(diagonal_1 == word &amp;&amp; diagonal_2 == word)
        |> Day_4.add_count(diagonal_1 == word &amp;&amp; diagonal_2 == word_reverse)
        |> Day_4.add_count(diagonal_1 == word_reverse &amp;&amp; diagonal_2 == word)
        |> Day_4.add_count(diagonal_1 == word_reverse &amp;&amp; diagonal_2 == word_reverse)
      end)
    end)
  end
end
Day_4.run_part1()
Day_4.run_part2()

Day 5

defmodule Day_5 do
  def input do
    """
    47|53
    97|13
    97|61
    97|47
    75|29
    61|13
    75|53
    29|13
    97|29
    53|29
    61|53
    97|53
    61|29
    47|13
    75|47
    97|75
    47|61
    75|61
    47|29
    75|13
    53|13

    75,47,61,53,29
    97,61,53,29,13
    75,29,13
    75,97,47,61,53
    61,13,29
    97,13,75,29,47
    """
  end

  def process_input() do
    File.read!("day5.txt")
    |> String.split("\n", trim: true)
    |> Enum.reduce({[], []}, fn i, {acc_x, acc_y} ->
      String.contains?(i, "|")
      |> if do
        [start, ends] =
          String.split(i, "|")
          |> Enum.map(&amp;String.to_integer(&amp;1))

        {[{start, ends} | acc_x], acc_y}
      else
        {acc_x, [String.split(i, ",") |> Enum.map(&amp;String.to_integer(&amp;1)) | acc_y]}
      end
    end)
    |> case do
      {ordering, print_order} ->
        {Enum.reverse(ordering)
         |> Day_5.to_map(), Enum.reverse(print_order)}
    end
  end

  def to_map(ordering) do
    Enum.reduce(ordering, %{}, fn {from, to}, acc ->
      Map.get_and_update(acc, from, fn curr ->
        if curr == nil do
          {curr, [to]}
        else
          {curr, [to | curr]}
        end
      end)
      |> elem(1)
    end)
  end

  def run_part1(print_order, ordering_map) do
    Enum.filter(print_order, fn list -> to_check?(list, ordering_map) end)
    |> get_sum()
  end

  def run_part2(print_order, ordering_map) do
    Enum.reject(print_order, fn list -> to_check?(list, ordering_map) end)
    |> Enum.map(fn list -> re_order(list, ordering_map) end)
    |> get_sum()
  end

  def to_check?([head | rest], map) do
    values = Map.get(map, head, [])

    Enum.all?(rest, fn r -> r in values end)
    |> if do
      if rest == [] do
        true
      else
        to_check?(rest, map)
      end
    else
      false
    end
  end

  def re_order(list, map, index \\ 0, acc \\ [])

  def re_order([], _, _, acc) do
    acc
  end

  def re_order(list, map, index, acc) do
    {key, rest} = List.pop_at(list, index)

    l = Map.get(map, key) || []

    Enum.all?(rest, fn r -> r in l end)
    |> if do
      re_order(rest, map, index, acc ++ [key])
    else
      if index + 1 >= length(list) do
        re_order(list, map, 0, acc)
      else
        re_order(list, map, index + 1, acc)
      end
    end
  end

  def get_sum(result) when is_list(result) do
    Enum.reduce(result, 0, fn list, sum ->
      index = trunc((length(list) - 1) / 2)
      sum + Enum.at(list, index)
    end)
  end
end
{ordering, print_order} = Day_5.process_input()
Day_5.run_part1(print_order, ordering)
Day_5.run_part2(print_order, ordering)