bjorng

bjorng

Erlang Core Team

Advent of Code 2024 - Day 2

Here is my solution for day 2 of Advent of Code:

https://github.com/bjorng/advent-of-code/blob/main/2024/day02/lib/day02.ex

Most Liked

dimitarvp

dimitarvp

Life sadly kept getting in the way but ultimately:

defmodule Day02 do
  @moduledoc ~S"""
  A solution to https://adventofcode.com/2024/day/2.
  """

  @type level :: pos_integer()
  @type distance :: integer()
  @type report :: [level()]

  @spec all_variants_with_one_element_removed(report()) :: [report()]
  def all_variants_with_one_element_removed(list) do
    for i <- 0..(length(list) - 1), do: list |> List.delete_at(i)
  end

  @spec sign(integer()) :: :zero | :minus | :plus
  def sign(0), do: :zero
  def sign(i) when i > 0, do: :plus
  def sign(i) when i < 0, do: :minus

  @spec distances(report()) :: [distance()]
  def distances([first | rest]) do
    rest
    |> Enum.reduce({first, []}, fn current_level, {previous_level, distances} ->
      {current_level, [current_level - previous_level | distances]}
    end)
    |> then(fn {_last_level, distances} -> Enum.reverse(distances) end)
  end

  @spec same_signs?([distance()]) :: boolean()
  def same_signs?(list) do
    list
    |> Enum.map(&sign/1)
    |> Enum.uniq()
    |> length()
    |> Kernel.==(1)
  end

  @spec safe?(report()) :: boolean()
  def safe?(report) do
    distances = distances(report)
    monotonical? = same_signs?(distances)

    safely_advancing? =
      distances |> Enum.map(&abs/1) |> Enum.all?(fn distance -> distance <= 3 end)

    monotonical? and safely_advancing?
  end

  @spec safe_with_a_dampener?(report()) :: boolean()
  def safe_with_a_dampener?(report) do
    safe?(report) or
      report |> all_variants_with_one_element_removed() |> Enum.any?(&safe?/1)
  end

  @doc ~S"""
  iex> Day02.part_1("7 6 4 2 1\n1 2 7 8 9\n9 7 6 2 1\n1 3 2 4 5\n8 6 4 4 1\n1 3 6 7 9\n")
  nil
  """
  @spec part_1(String.t()) :: non_neg_integer()
  def part_1(input \\ Aoc.input(2)) do
    input
    |> Aoc.parse_lines_of_integers()
    |> Enum.count(&safe?/1)
  end

  @spec part_2(String.t()) :: non_neg_integer()
  def part_2(input \\ Aoc.input(2)) do
    input
    |> Aoc.parse_lines_of_integers()
    |> Enum.count(&safe_with_a_dampener?/1)
  end
end

Did my best to make it readable and intuitive, even benchmarked three competing implementation I had ideas about, and only posted the one that won.

lud

lud

No optimization here, just building all possible list before trying them one by one :smiley:

defmodule AdventOfCode.Solutions.Y24.Day02 do
  alias AoC.Input

  def parse(input, _part) do
    Enum.map(Input.stream!(input, trim: true), &parse_line/1)
  end

  defp parse_line(line) do
    Enum.map(String.split(line, " "), &String.to_integer/1)
  end

  def part_one(problem) do
    problem
    |> Enum.filter(&safe?/1)
    |> length()
  end

  defp safe?([a, b | _] = list) when a < b, do: safe?(:asc, list)
  defp safe?([a, b | _] = list) when a > b, do: safe?(:desc, list)
  defp safe?([a, a | _]), do: false

  defp safe?(:asc, [a, b | rest]) when abs(a - b) in 1..3 and a < b, do: safe?(:asc, [b | rest])
  defp safe?(:desc, [a, b | rest]) when abs(a - b) in 1..3 and a > b, do: safe?(:desc, [b | rest])
  defp safe?(_, [_last]), do: true
  defp safe?(_, _), do: false

  def part_two(problem) do
    problem
    |> Enum.filter(&safeish?/1)
    |> length()
  end

  defp safeish?(list) do
    candidates = [list | Enum.map(0..(length(list) - 1), &List.delete_at(list, &1))]
    Enum.any?(candidates, &safe?/1)
  end
end

Edit:

candidates = Stream.concat([list], Stream.map(0..(length(list) - 1), &List.delete_at(list, &1)))

This would save memory but given the input size it’s actually slower.

Where Next?

Trending in Challenges Top

sevenseacat
Well some of us wanted a difficulty spike - and today we got one :sweat_smile: https://github.com/sevenseacat/advent_of_code/blob/main/l...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
kip
Localize is the next generation localisation library for Elixir. Think of it as ex_cldr version 3.0. The first version will be released ...
New
webofbits
Squid Mesh is an open source workflow automation runtime for Elixir applications. It is aimed at Phoenix and OTP apps that want to defin...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
kip
In 2021 I started a new library called Tempo with the objective of modelling time as a set of intervals - not as instants. In 2022 I gave...
New

We're in Beta

About us Mission Statement