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

Advent of Code 2023

solutions/elixir/aoc.livemd

Advent of Code 2023

Mix.install([
  {:kino_aoc, "~> 0.1"}
])

Utils

defmodule AOC do
  def solve(module) do
    for part <- 1..2 do
      apply(module, :solve, [part])
      |> then(&amp;IO.inspect("Part #{part}: #{inspect(&amp;1)}"))
    end
  end
end

Day 1

{:ok, day_1_input} =
  KinoAOC.download_puzzle("2023", "1", System.fetch_env!("LB_AOC_SESSION"))
defmodule Day1 do
  @input day_1_input |> String.split()
  @digits_table ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
                |> Enum.with_index()
                |> Map.new(fn {k, v} -> {k, "#{v + 1}"} end)

  def solve(1) do
    @input
    |> Enum.map(&amp;get_digits/1)
    |> Enum.sum()
  end

  def solve(2) do
    @input
    |> Enum.map(&amp;normalize/1)
    |> Enum.sum()
  end

  defp get_digits(line) do
    Regex.scan(~r/\d/, line)
    |> then(fn ln -> List.first(ln) ++ List.last(ln) end)
    |> Enum.join()
    |> String.to_integer()
  end

  defp normalize(line) do
    pattern = @digits_table |> Map.keys() |> Enum.join("|")

    first =
      ("[0-9]|" <> pattern)
      |> Regex.compile!()
      |> Regex.run(line)
      |> List.first()
      |> convert_digit()

    last =
      ("[0-9]|" <> (pattern |> String.reverse()))
      |> Regex.compile!()
      |> Regex.run(line |> String.reverse())
      |> List.first()
      |> String.reverse()
      |> convert_digit()

    (first <> last) |> String.to_integer()
  end

  defp convert_digit(word) do
    if Enum.member?(Map.values(@digits_table), word) do
      word
    else
      Map.get(@digits_table, word)
    end
  end
end

AOC.solve(Day1)

Day 2

{:ok, day_2_input} =
  KinoAOC.download_puzzle("2023", "2", System.fetch_env!("LB_AOC_SESSION"))
defmodule Day2 do
  @input day_2_input
  @limits %{
    "red" => 12,
    "green" => 13,
    "blue" => 14
  }

  def solve(1) do
    @input
    |> parse_games()
    |> Enum.map(&amp;are_rounds_possible?/1)
    |> Enum.with_index(1)
    |> Enum.reduce(0, fn {possible?, idx}, acc ->
      if possible?, do: acc + idx, else: acc
    end)
  end

  def solve(2) do
    @input
    |> parse_games()
    |> Enum.map(&amp;minimum_cubes/1)
    |> Enum.map(fn rounds ->
      rounds
      |> Enum.map(fn {_color, count} -> count end)
      |> Enum.product()
    end)
    |> Enum.sum()
  end

  defp parse_games(input) do
    for game <- String.splitter(input, "\n", trim: true) do
      ~r/(?:Game \d+: )(.*)/
      |> Regex.run(game, capture: :all_but_first)
      |> List.first()
      |> String.split(";\s")
    end
  end

  defp are_rounds_possible?(rounds) do
    for round <- rounds do
      String.split(round, ",\s")
      |> Enum.map(fn turn ->
        [num, color] = String.split(turn)
        Map.get(@limits, color) >= String.to_integer(num)
      end)
      |> Enum.all?()
    end
    |> Enum.all?()
  end

  defp minimum_cubes(rounds) do
    rounds
    |> Enum.flat_map(&amp;String.split(&amp;1, ",\s"))
    |> Enum.group_by(
      fn turn -> Regex.run(~r/(red|green|blue)/, turn) |> List.first() end,
      fn turn -> Regex.run(~r/\d+/, turn) |> List.first() |> String.to_integer() end
    )
    |> Enum.map(fn {k, v} -> {k, Enum.max(v)} end)
  end
end

AOC.solve(Day2)

Day 4

{:ok, day_4_input} =
  KinoAOC.download_puzzle("2023", "4", System.fetch_env!("LB_AOC_SESSION"))
day_4_sample =
  """
  Card 1: 41 48 83 86 17 | 83 86  6 31 17  9 48 53
  Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
  Card 3:  1 21 53 59 44 | 69 82 63 72 16 21 14  1
  Card 4: 41 92 73 84 69 | 59 84 76 51 58  5 54 83
  Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
  Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
  """
  |> String.trim()
defmodule Day4 do
  @input day_4_input |> String.split("\n", trim: true)
  def solve(1) do
    @input
    |> Enum.map(fn card ->
      card
      |> String.replace(~r/^Card\s+\d+:\s+/, "")
      |> String.split(" | ")
      |> Enum.map(&amp;string_to_set/1)
      |> then(&amp;apply(MapSet, :intersection, &amp;1))
      |> MapSet.size()
    end)
    |> Enum.filter(fn x -> x > 0 end)
    |> Enum.map(fn winners ->
      for n <- 1..winners, reduce: 1 do
        acc -> if n == 1, do: acc, else: acc * 2
      end
    end)
    |> Enum.sum()
  end

  defp string_to_set(s) do
    String.split(s) |> Enum.map(&amp;String.to_integer/1) |> MapSet.new()
  end
end

Day4.solve(1)