bjorng

bjorng

Erlang Core Team

Advent of Code 2024 - Day 2

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

Most Liked

sevenseacat

sevenseacat

Author of Ash Framework
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.

Aetherus

Aetherus

I was trying hard to find a smart-ass solution for part 2 without using List.delete_at/2, but in the end, I had to admit that I’m not that smart after all.

Part 1

puzzle_input
|> String.split("\n")
|> Enum.map(&String.split/1)
|> Enum.map(&Enum.map(&1, fn s -> String.to_integer(s) end))
|> Enum.count(fn
  [a, a | _] ->
    false
  
  [a, b | _] = line ->
    sign = div(a - b, abs(a - b))
  
    line
    |> Enum.chunk_every(2, 1, :discard)
    |> Enum.all?(fn [a, b] ->
      sign * (a - b) in 1..3
    end)
end)

Part 2

puzzle_input
|> String.split("\n")
|> Enum.map(&String.split/1)
|> Enum.map(&Enum.map(&1, fn s -> String.to_integer(s) end))
|> Enum.count(fn line ->
  0..length(line)
  |> Stream.map(&List.delete_at(line, &1))
  |> Enum.any?(fn
    [a, a | _] ->
      false
  
    [a, b | _] = line ->
      sign = div(a - b, abs(a - b))
  
      line
      |> Enum.chunk_every(2, 1, :discard)
      |> Enum.all?(fn [a, b] ->
        sign * (a - b) in 1..3
      end)
  end)
end)
lkuty

lkuty

#!/usr/bin/env elixir
# 2024. day 2.

defmodule A do
  @spec is_ok?([integer()]) :: boolean()
  def is_ok?(lst), do: is_ok?(lst, nil, nil)

  @spec is_ok?([integer()], nil | integer(), nil | :inc | :dec) :: boolean()
  defp is_ok?(lst, prev, dir)

  defp is_ok?([], _, _), do: true # empty list
  defp is_ok?([_fst], nil, _), do: true # single element list
  defp is_ok?([fst | rest], nil, nil), do: is_ok?(rest, fst, nil) # more than one element

  # same number!
  defp is_ok?([fst | _rest], fst, _dir), do: false

  # direction is unknown
  defp is_ok?([fst | rest], prev, nil) when fst < prev, do: (if prev-fst<=3, do: is_ok?(rest, fst, :dec), else: false)
  defp is_ok?([fst | rest], prev, nil) when fst > prev, do: (if fst-prev<=3, do: is_ok?(rest, fst, :inc), else: false)

  # direction is decreasing
  defp is_ok?([fst | rest], prev, :dec) when fst < prev, # we keep on decreasing
    do: (if prev-fst<=3, do: is_ok?(rest, fst, :dec), else: false)
  defp is_ok?([fst | _rest], prev, :dec) when fst > prev, do: false # we start to increase

  # direction is increasing
  defp is_ok?([fst | rest], prev, :inc) when fst > prev,
    do: (if fst-prev<=3, do: is_ok?(rest, fst, :inc), else: false) # we keep on increasing
  defp is_ok?([fst | _rest], prev, :inc) when fst < prev, do: false # we start to decrease

  # get all the sublists of lst with one element less than lst
  # A.sublists([1,2,3,4]) => [[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]
  def sublists(lst), do: sublists([], lst, [])
  def sublists(_pre, [], acc), do: acc
  def sublists(pre, [a | rest], acc), do: sublists([a | pre], rest, [(Enum.reverse(pre) ++ rest) | acc])
end

File.stream!("day02.txt")
|> Stream.map(fn line -> String.split(line) |> Enum.map(&String.to_integer/1) |> A.is_ok?() end)
|> Enum.count(&Function.identity/1)
|> IO.inspect(label: "part 1")

File.stream!("day02.txt")
|> Stream.map(fn line ->
  lst = String.split(line) |> Enum.map(&String.to_integer/1)
  Enum.any?([lst | A.sublists(lst)], fn lst -> A.is_ok?(lst) end)
end)
|> Enum.count(&Function.identity/1)
|> IO.inspect(label: "part 2")

Where Next?

Popular in Challenges Top

ehayun
I have 2 arrays: a1 can be any combination of value or nil like that a1 = [1,nil,3] and array 2 the same a2 = [4,2, nil] How do I com...
New
Aetherus
Today’s challenge is quite interesting. I ended up using Zipper to solve this problem. Maybe I overengineered quite a bit. The data stru...
New
bismark
Took me a minute to remember my binary math :smile: :grimacing:… import Bitwise __DIR__ |&gt; Path.join("puzzle.txt") |&gt; File.stream...
New
sneako
Note by the Moderators: This topic is to talk about the first day of the Advent of Code. For general discussion about the Advent of Code...
New
lud
At first I was scared but I found is a simple way to compute the sides. defmodule AdventOfCode.Solutions.Y24.Day12 do alias AdventOfCo...
New
bjorng
Note: This topic is to talk about Day 3 of the Advent of Code 2019 . There is a private leaderboard for elixirforum members. You can jo...
New
cblavier
Hey there :wave: No magic or algorithmic finesse today, I just finished the challenge and I my code is quite slow (1sec for part1, 3se...
New
Aetherus
Hello, guys. I’m back again, but only for the weekends, maybe. This topic is about Day 13 of the Advent of Code 2020 . Thanks to @egze,...
New
kwando
Took a while, but another use case for “move vectors” today and pattern matching. :slight_smile: The trick was to first generate a list...
New
Aetherus
I spent 3 hours struggling in part 2, until I noticed a very basic mistake :joy: Here’s my code: By the way, the starting position in...
New

Other popular topics Top

albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; someth...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New

We're in Beta

About us Mission Statement