lud

lud

Hello everyone!

This year is going to be shorter, but the difficulty will grow faster. Today I already feel that this is not standard “Day 1” difficulty.

Or I am really overcomplicating things. Didn’t sleep well :smiley:

defmodule AdventOfCode.Solutions.Y25.Day01 do
  alias AoC.Input

  def parse(input, :part_one) do
    input
    |> Input.stream!(trim: true)
    |> Enum.map(fn
      "R" <> n -> {:right, String.to_integer(n)}
      "L" <> n -> {:left, String.to_integer(n)}
    end)
  end

  def parse(input, :part_two) do
    input
    |> Input.stream!(trim: true)
    |> Enum.flat_map(fn
      "R" <> n -> expand_rotation(:right, String.to_integer(n))
      "L" <> n -> expand_rotation(:left, String.to_integer(n))
    end)
  end

  defp expand_rotation(direction, amount) when amount <= 100 do
    [{direction, amount}]
  end

  defp expand_rotation(direction, amount) when amount > 100 do
    [{direction, 100} | expand_rotation(direction, amount - 100)]
  end

  def part_one(problem) do
    problem
    |> Stream.scan(50, fn
      {:left, n}, acc -> Integer.mod(acc - n, 100)
      {:right, n}, acc -> Integer.mod(acc + n, 100)
    end)
    |> Enum.count(&(&1 == 0))
  end

  def part_two(problem) do
    problem
    |> Stream.transform(50, fn
      {:left, n}, acc ->
        new_acc = Integer.mod(acc - n, 100)
        {[{:left, acc, new_acc}], new_acc}

      {:right, n}, acc ->
        new_acc = Integer.mod(acc + n, 100)
        {[{:right, acc, new_acc}], new_acc}
    end)
    |> Enum.count(fn
      {_, _, 0} -> true
      {_, 0, _} -> false
      {:right, a, b} when a > b -> true
      {:left, a, b} when a < b -> true
      {_, same, same} -> true
      _ -> false
    end)
  end
end

Showing Posts 1 to 10

code-shoily

code-shoily

This year I am using Clojure. But I did something similar. Had to look for all the cases:

  1. Get the zero passed by div value and 100
  2. Is the number already starting at 0 and current diff is negative? The nothing changes, otherwise, incr the zero values
  3. Is the difference after rotation more than 100? Then we definitely crossed zero one more time - incr the zero values
  4. Is the diff zero? Yup, increment again!

That was how I did the second part. One interesting thing, if you miss 2 or 4 - the example inputs give you 6 - but the final input doesn’t match, I wonder if that was intentional.

Another thing, that reminds me of the sevenine situation in 2023 - the > 100 cases was hidden from example input. Sneaky lol.

I have a feeling I can get a formula for this. Will be following y’all to see how you did it.

bjorng

bjorng

Erlang Core Team

Yes, definitely a little bit harder, especially part 2.

defmodule Day01 do
  def part1(input) do
    parse(input)
    |> Enum.scan(50, &(Integer.mod(&1 + &2, 100)))
    |> Enum.count(&(&1 === 0))
  end

  def part2(input) do
    parse(input)
    |> Enum.reduce({50, 0}, fn amount, {dial, crossings} ->
      new_dial = dial + rem(amount, 100)

      crossings = crossings + div(abs(amount), 100)

      crossings = cond do
        dial === 0 ->
          crossings
        new_dial === 0 or new_dial === 100 ->
          crossings
        new_dial in 1..99 ->
          crossings
        true ->
          crossings + 1
      end

      new_dial = Integer.mod(new_dial, 100)
      crossings = if new_dial === 0 do
        crossings + 1
      else
        crossings
      end
      {new_dial, crossings}
    end)
    |> then(&elem(&1, 1))
  end

  defp parse(input) do
    input
    |> Enum.map(fn line ->
      case line do
        "L" <> int -> -String.to_integer(int)
        "R" <> int -> String.to_integer(int)
      end
    end)
  end
end

sevenseacat

sevenseacat

Author of Ash Framework

oh, so many off-by-one errors in part 2. I’m a wee bit out of practice!

https://github.com/sevenseacat/advent_of_code/blob/main/lib/y2025/day01.ex

My insight was that wrapping from 0 → 99 → 00 doesn’t actually matter - all that matters is whether or not the current position is divisible by 100.

hauleth

hauleth

Part 2 was PITA, but managed:

instructions =
  puzzle_input
  |> String.split("\n", trim: true)
  |> Enum.map(fn
    <<dir>> <> rest ->
      num = String.to_integer(rest)
      if dir == ?R do
        {div(num, 100), rem(num, 100)}
      else
        {div(num, 100), -rem(num, 100)}
      end
  end)

## Part 1

Enum.reduce(instructions, {50, 0}, fn {_rot, val}, {curr, sum} ->
  next = Integer.mod(curr + val, 100)

  {next, sum + if(next == 0, do: 1, else: 0)}
end)
|> elem(1)

## Part 2

Enum.reduce(instructions, {50, 0}, fn {rot, val}, {curr, sum} ->
  next = curr + val
  pass =
    cond do
      curr == 0 and next < 0 -> 0
      next not in 0..99 -> 1
      rem(next, 100) == 0 -> 1
      true -> 0
    end

  {Integer.mod(next, 100), sum + pass + rot}
end)
|> elem(1)
rvnash

rvnash

Not too hard, had to wrap my head around how many zero crossing occur when turning left.

defmodule RAoc.Solutions.Y25.Day01 do
  alias AoC.Input

  def parse(input, _part) do
    Input.stream!(input, trim: true)
    |> Enum.to_list()
  end

  def part_one(problem) do
    state = run(problem)
    state.zeroes
  end

  defp run(turns) do
    state = %{dial: 50, zeroes: 0, any_zero: 0}

    Enum.reduce(turns, state, fn <<dir::binary-size(1), sdist::binary>>,
                                 %{dial: dial, zeroes: zeroes, any_zero: any_zero} ->
      dist = String.to_integer(sdist)

      {new_dial, zero_crossing_count} =
        case dir do
          "L" ->
            {Integer.mod(dial - dist, 100), div(Integer.mod(100 - dial, 100) + dist, 100)}

          "R" ->
            {Integer.mod(dial + dist, 100), div(dial + dist, 100)}
        end

      %{
        dial: new_dial,
        zeroes: if(new_dial == 0, do: zeroes + 1, else: zeroes),
        any_zero: zero_crossing_count + any_zero
      }
    end)
  end

  def part_two(problem) do
    state = run(problem)
    state.any_zero
  end
end
tnlogy

tnlogy

Hi! Nice to see some solutions to compare with since this is my first time using Elixir. I like it so far :).

This is my solution for day1, I tried to simplify part 2 by replacing the instructions with just +1 and -1. Maybe some of my code is a bit strange since I’m new to Elixir.

defmodule Advent2025Test do
  use ExUnit.Case
  doctest Advent2025

  def day1_data() do
    "day1.txt"
    |> File.stream!()
    |> Stream.map(fn line -> String.split_at(line, 1) end)
    |> Stream.map(fn {dir, num} -> {dir, String.to_integer(String.trim(num))} end)
  end

  def day1_count(data) do
    data
    |> Enum.reduce({50, 0}, fn {dir, num}, {val, res} ->
      Integer.mod(
        val +
          case dir do
            "L" -> -num
            "R" -> num
          end,
        100
      )
      |> then(fn new_val ->
        {new_val, if(new_val == 0, do: res + 1, else: res)}
      end)
    end)
  end

  def day1_make_ticks(data) do
    data
    |> Stream.flat_map(fn {dir, num} ->
      Stream.map(1..num, fn _ -> {dir, 1} end)
    end)
  end

  test "day1_p1" do
    {_, zeroes} = day1_data() |> day1_count()
    IO.puts("answer #{zeroes}")
    assert zeroes == 1048
  end

  test "day1_p2" do
    # Same as part1, but make a list of just {"L", 1} and {"R", 1}
    {_, zeroes} = day1_data() |> day1_make_ticks() |> day1_count()
    IO.puts("answer: #{zeroes}")
    assert zeroes == 6498
  end
end

ken-kost

ken-kost

Not super satisfied with my solution. :cat_face:

defmodule Aoc2025.Solutions.Y25.Day01 do
  alias AoC.Input

  def parse(input, _part) do
    input
    |> Input.read!()
    |> String.trim()
    |> String.split("\n")
    |> Enum.map(fn line ->
      case line do
        "L" <> value -> {:left, String.to_integer(value)}
        "R" <> value -> {:right, String.to_integer(value)}
      end
    end)
  end

  def part_one(problem) do
    count_equilibria(problem, 50, 0, _count_passes? = false)
  end

  def part_two(problem) do
    count_equilibria(problem, 50, 0, _count_passes? = true)
  end

  defp count_equilibria([], _, solution, _count_passes?), do: solution

  defp count_equilibria([{direction, value} | rest], current_position, acc, true) do
    case calculate_position_counting_passes(current_position, direction, value) do
      {0, passes} ->
        count_equilibria(rest, 0, acc + passes, true)

      {new_position, passes} ->
        count_equilibria(rest, new_position, acc + passes, true)
    end
  end

  defp count_equilibria([{direction, value} | rest], current_position, acc, false) do
    case calculate_position(current_position, direction, value) do
      0 -> count_equilibria(rest, 0, acc + 1, false)
      new_position -> count_equilibria(rest, new_position, acc, false)
    end
  end

  defp calculate_position(current_position, :left, value) do
    value = rem(value, 100)

    cond do
      value > current_position ->
        100 - (value - current_position)

      true ->
        current_position - value
    end
  end

  defp calculate_position(current_position, :right, value) do
    rem(current_position + value, 100)
  end

  defp calculate_position_counting_passes(current_position, :left, value) do
    {value, passes} = custom_rem(value, 100)

    cond do
      current_position == 0 ->
        {100 - value, passes}

      value > current_position ->
        {100 - (value - current_position), passes + 1}

      true ->
        passes = if current_position - value == 0, do: passes + 1, else: passes
        {current_position - value, passes}
    end
  end

  defp calculate_position_counting_passes(current_position, :right, value) do
    custom_rem(current_position + value, 100)
  end

  defp custom_rem(dividend, divisor) do
    {rem(dividend, divisor), div(dividend, divisor)}
  end
end

nico_amsterdam

nico_amsterdam

Lots of mod and div’s.
Insight in part 2: when the dail was pointing at 0, it is not an extra click when it goes to a negative number.

Solution in Livebook:

# Advent of code 2025 day 1

```elixir
Mix.install([
  {:kino, "~> 0.18"}
])
```

## Part 1

https://adventofcode.com/2025/day/1

```elixir
input = Kino.Input.textarea("Please give me input:")
```

<!-- livebook:{"reevaluate_automatically":true} -->

```elixir
rotations =
  Kino.Input.read(input)
  |> String.split("\n", trim: true)
  |> Enum.map(fn
    "L" <> rotate -> {-1, String.to_integer(rotate)}
    "R" <> rotate -> {1, String.to_integer(rotate)}
  end)

length(rotations)
```

<!-- livebook:{"reevaluate_automatically":true} -->

```elixir
Enum.reduce(rotations, {0, 50}, fn {sign, rotate}, {null_count, dail_position} ->
  new_position = dail_position + sign * Integer.mod(rotate, 100)
  new_position = if new_position < 0, do: new_position + 100, else: Integer.mod(new_position, 100)
  new_null_count = if new_position == 0, do: null_count + 1, else: null_count
  {new_null_count, new_position}
end)

# prints {null count, last position}
```

## Part 2

<!-- livebook:{"reevaluate_automatically":true} -->

```elixir
Enum.reduce(rotations, {0, 50}, fn {sign, rotate}, {null_count, dail_position} ->
  new_position = dail_position + sign * Integer.mod(rotate, 100)
  extra_counts = div(rotate, 100)
  new_null_count = if (new_position <= 0 and dail_position != 0) or new_position >= 100, do: null_count + 1, else: null_count
  new_position = if new_position < 0, do: new_position + 100, else: Integer.mod(new_position, 100)
  {new_null_count + extra_counts, new_position}
end)

# prints {null count, last position}
```

KeithFrost

KeithFrost

Part 1

defmodule Combo do
  @modulus 100
  
  def parse(input) do
    Regex.scan(~r"(R|L)(\d+)", input)
      |> Enum.map(fn [_match, dir, n_str] ->
        n = String.to_integer(n_str)
        case dir do
          "R" ->
            n
          "L" ->
            -n
        end
      end)
  end

  def decode(seq) do
    {zeros, _sum} = Enum.reduce(seq, {0, div(@modulus, 2)}, fn clicks, {zs, sum} ->
      sum = rem(sum + clicks, @modulus)
      sum = rem(sum + @modulus, @modulus)
      zeros = if sum == 0 do
        zs + 1
      else
        zs
      end
      {zeros, sum}
    end)
    zeros
  end
end

Part 2

defmodule Combo2 do
  @modulus 100
  
  def decode(seq) do
    {zeros, _sum} = Enum.reduce(seq, {0, div(@modulus, 2)}, fn clicks, {zs, sum} ->
      full_spins = abs(div(clicks, @modulus))
      rem_clicks = rem(clicks, @modulus)
      nsum = sum + rem_clicks
      zeros = if nsum >= @modulus or nsum <= 0 and sum > 0 do
        zs + full_spins + 1
      else
        zs + full_spins
      end
      {zeros, rem(nsum + @modulus, @modulus)}
    end)
    zeros
  end
end
billylanchantin

billylanchantin

Boilerplate

def file_to_lines(file), do: file |> File.read!() |> String.trim() |> String.split("\n")
def file_to_lines(file, fun), do: file |> file_to_lines() |> Enum.map(fun)

Part 1

def part1(file), do: file |> file_to_lines(&parse/1) |> count0()
def parse(l), do: l |> String.replace("R", "") |> String.replace("L", "-") |> Integer.parse() |> elem(0)
def count0(i), do: i |> Enum.scan(50, &Integer.mod(&1 + &2, 100)) |> Enum.count(&(&1 == 0))

Enum.scan/3 works really well here.

Part 2

def part2(file), do: file |> file_to_lines(&parse/1) |> Stream.flat_map(&flatten/1) |> count0()
def flatten(x), do: if(x < 0, do: List.duplicate(-1, abs(x)), else: List.duplicate(1, x))

It’s tempting to do math, but reusing prior work is simpler if less performant.

Where Next? Top

Trending in Challenges Top

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
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
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews