Powered by AppSignal & Oban Pro

Day 10: Syntax Scoring

2021/day_10_syntax_scoring.livemd

Day 10: Syntax Scoring

Mix.install([:kino])

input = Kino.Input.textarea("Please paste your input:")

Setup

inputs =
  input
  |> Kino.Input.read()
  |> String.split("\n", trim: true)

Part 1

https://adventofcode.com/2021/day/10

Solve: Part 1

defmodule Day10.Part1 do
  @opens ["(", "[", "{", "<"]
  @closes [")", "]", "}", ">"]

  @score_map %{
    ")" => 3,
    "]" => 57,
    "}" => 1197,
    ">" => 25137
  }

  def analyze(line) do
    line
    |> String.graphemes()
    |> Enum.reduce_while([], fn char, stack ->
      case char do
        char when char in @opens ->
          {:cont, [char | stack]}

        char when char in @closes ->
          [current | rest_stack] = stack

          case chunk?(current, char) do
            true -> {:cont, rest_stack}
            false -> {:halt, {:corrupted, char}}
          end
      end
    end)
    |> case do
      {:corrupted, first_illigal_character} -> {:corrupted, first_illigal_character}
      _ -> :ok
    end
  end

  def get_score(char), do: @score_map[char]

  defp chunk?("(", ")"), do: true
  defp chunk?("[", "]"), do: true
  defp chunk?("{", "}"), do: true
  defp chunk?("<", ">"), do: true
  defp chunk?(_, _), do: false
end

inputs
|> Enum.map(&Day10.Part1.analyze/1)
|> Enum.filter(fn
  {:corrupted, _} -> true
  _ -> false
end)
|> Enum.map(fn {:corrupted, first_illegal_character} ->
  Day10.Part1.get_score(first_illegal_character)
end)
|> Enum.sum()

Part 2

https://adventofcode.com/2021/day/10#part2

defmodule Day10.Part2 do
  @opens ["(", "[", "{", "<"]
  @closes [")", "]", "}", ">"]

  @score_map %{
    "(" => 1,
    "[" => 2,
    "{" => 3,
    "<" => 4
  }

  def analyze(line) do
    line
    |> String.graphemes()
    |> Enum.reduce_while([], fn char, stack ->
      case char do
        char when char in @opens ->
          {:cont, [char | stack]}

        char when char in @closes ->
          [current | rest_stack] = stack

          case chunk?(current, char) do
            true -> {:cont, rest_stack}
            false -> {:halt, {:corrupted, char}}
          end
      end
    end)
    |> case do
      {:corrupted, first_illigal_character} -> {:corrupted, first_illigal_character}
      [_ | _] = rest_stack -> {:incompleted, rest_stack}
      _ -> :ok
    end
  end

  def get_score(stack) do
    stack
    |> Enum.reduce(0, fn open, score ->
      score * 5 + @score_map[open]
    end)
  end

  defp chunk?("(", ")"), do: true
  defp chunk?("[", "]"), do: true
  defp chunk?("{", "}"), do: true
  defp chunk?("<", ">"), do: true
  defp chunk?(_, _), do: false
end

scores =
  inputs
  |> Enum.map(&Day10.Part2.analyze/1)
  |> Enum.filter(fn
    {:incompleted, _} -> true
    _ -> false
  end)
  |> Enum.map(fn {:incompleted, rest_stack} -> Day10.Part2.get_score(rest_stack) end)
  |> Enum.sort()

middle_score = Enum.at(scores, div(Enum.count(scores) - 1, 2))