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

Advent of Code - 2024 - Day 2

advent-of-code/2024/day02.livemd

Advent of Code - 2024 - Day 2

Mix.install([
  {:kino, "~> 0.14.2"},
  {:kino_vega_lite, "~> 0.1.13"},
  {:kino_explorer, "~> 0.1.23"}
])

Puzzle Input

puzzle_input = Kino.Input.textarea("Please paste the puzzle input:")
for {x, i} <- Enum.with_index([9, 10, 11, 12, 13]) do
  {x, i}
end
list = [11, 12, 13, 14, 15]

for i <- 0..Enum.count(list) - 1 do
  List.delete_at(list, i)
end

Part 1

part_1_test_input = Kino.Input.textarea("Please paste the test input for part 1:")
defmodule NuclearReport do
  defstruct [:data, :categorization]

  def new(data) do
    %__MODULE__{data: data}
  end

  def categorize(report = %__MODULE__{data: data}) do
    %{report | categorization: categorize(data)}
  end

  def categorize(data) do
    data
    |> Enum.chunk_every(2, 1, :discard)
    |> Enum.reduce_while(:unknown, fn [left, right], direction ->
      change = left - right

      if abs(change) <= 0 or abs(change) > 3 do
        {:halt, :unsafe}
      else
        case direction do
          :unknown ->
            direction =
              if change > 0 do
                :positive
              else
                :negative
              end

            {:cont, direction}

          :positive ->
            if change < 0 do
              {:halt, :unsafe}
            else
              {:cont, direction}
            end

          :negative ->
            if change > 0 do
              {:halt, :unsafe}
            else
              {:cont, direction}
            end
        end
      end
    end)
    |> then(fn
      :negative -> :safe
      :positive -> :safe
      result -> result
    end)
  end

  def apply_problem_damper(report = %__MODULE{data: data}) do
    possibilities =
      for i <- 0..(Enum.count(data) - 1) do
        List.delete_at(data, i)
      end

    damper_result =
      possibilities
      |> Enum.reduce_while(:unsafe, fn possible_data, result ->
        if result == :safe do
          {:halt, result}
        else
          {:cont, categorize(possible_data)}
        end
      end)

    %{report | categorization: damper_result}
  end
end
defmodule NuclearReports do
  def parse(input) do
    input
    |> String.split("\n")
    |> Enum.map(fn line ->
      String.split(line)
      |> Enum.map(&amp;String.to_integer/1)
    end)
    |> Enum.map(&amp;NuclearReport.new/1)
  end

  def categorize(reports) do
    reports
    |> Enum.map(&amp;NuclearReport.categorize/1)
  end

  def apply_problem_damper(reports) do
    grouped = NuclearReports.group_by_categorization(reports)

    updated =
      grouped[:unsafe]
      |> Enum.map(&amp;NuclearReport.apply_problem_damper/1)
      |> NuclearReports.group_by_categorization()

    grouped
    |> Map.put(:unsafe, updated[:unsafe])
    |> Map.put(:safe, grouped[:safe] ++ updated[:safe])
    |> Map.values()
    |> List.flatten()
  end

  def group_by_categorization(reports) do
    Enum.group_by(reports, &amp; &amp;1.categorization)
  end
end
part_1_test_input
|> Kino.Input.read()
|> NuclearReports.parse()
|> NuclearReports.categorize()
|> Enum.count(fn report -> report.categorization == :safe end)
puzzle_input
|> Kino.Input.read()
|> NuclearReports.parse()
|> NuclearReports.categorize()
|> Enum.count(fn report -> report.categorization == :safe end)

Part 2

part_2_test_input = Kino.Input.textarea("Please paste the test input for part 2:")
part_2_test_input
|> Kino.Input.read()
|> NuclearReports.parse()
|> NuclearReports.categorize()
|> NuclearReports.apply_problem_damper()
|> Enum.count(fn report -> report.categorization == :safe end)
puzzle_input
|> Kino.Input.read()
|> NuclearReports.parse()
|> NuclearReports.categorize()
|> NuclearReports.apply_problem_damper()
|> Enum.count(fn report -> report.categorization == :safe end)