bjorng

bjorng

Erlang Core Team

“When in doubt, use brute force.” – Ken Thompson

defmodule Day02 do
  def part1(input) do
    solve(input, &invalid_part1?/1)
  end

  defp invalid_part1?(n) when is_integer(n) and n > 0 do
    cond do
      n in 10..99 ->
        div(n, 10) === rem(n, 10)
      n in 1000..9999 ->
        div(n, 100) === rem(n, 100)
      n in 100_000..999_999 ->
        div(n, 1000) === rem(n, 1000)
      n in 10_000_000..99_999_999 ->
        div(n, 10_000) === rem(n, 10_000)
      n in 1_000_000_000..9_999_999_999 ->
        div(n, 100_000) === rem(n, 100_000)
      true -> false
    end
  end

  def part2(input) do
    solve(input, &invalid_part2?/1)
  end

  defp invalid_part2?(n) when is_integer(n) and n > 0 do
    powers = [10, 100, 1000, 10000, 100000, 1000000]
    Enum.any?(powers, fn power ->
      part = rem(n, power)
      if part < div(power, 10) do
        false
      else
        case count_parts(div(n, power), power, part, 1) do
          nil -> false
          num_parts -> num_parts >= 2
        end
      end
    end)
  end

  defp count_parts(0, _power, _part, num_parts), do: num_parts
  defp count_parts(n, power, part, num_parts) do
    case rem(n, power) do
      ^part ->
        count_parts(div(n, power), power, part, num_parts + 1)
      _ ->
        nil
    end
  end

  defp solve(input, invalid) do
    parse(input)
    |> Enum.flat_map(&expand_range(&1, invalid))
    |> Enum.sum
  end

  defp expand_range(r, invalid) do
    Enum.flat_map(r, fn n ->
      case invalid.(n) do
        true -> [n]
        false -> []
      end
    end)
  end

  defp parse(input) do
    input
    |> Enum.flat_map(fn line ->
      line
      |> String.split(",")
      |> Enum.map(fn range ->
        range
        |> String.split("-")
        |> then(fn [first, last] ->
          String.to_integer(first) .. String.to_integer(last)
        end)
      end)
    end)
  end
end

Showing Posts 1 to 10

code-shoily

code-shoily

I just halved the string and checked for both halves being equal for part 1 and good old regex for part 2.

KeithFrost

KeithFrost

2025 Dec 02

Gift Shop

defmodule ProductId do
  def parse_range(s) do
    case Regex.run(~r"(\d+)-(\d+)", s) do
      [_match, ls, hs] ->
        String.to_integer(ls)..String.to_integer(hs)
    end
  end
  def parse_ranges(input) do 
    String.split(input, ",")
      |> Enum.map(&parse_range/1)
  end

  def invalid1?(id) do
    s = Integer.to_string(id)
    l = String.length(s)
    if Bitwise.band(l, 1) == 1 do
      false
    else
      {s1, s2} = String.split_at(s, div(l, 2))
      s1 == s2
    end
  end

  def invalids(seqs, invalid? \\ &invalid1?/1) do
    Task.async_stream(seqs, fn seq ->
      Enum.filter(seq, invalid?)
    end) |> 
      Enum.flat_map(fn {:ok, invs} -> invs end)
  end
end
test_ranges = """
11-22,95-115,998-1012,1188511880-1188511890,222220-222224,
1698522-1698528,446443-446449,38593856-38593862,565653-565659,
824824821-824824827,2121212118-2121212124
""" |> ProductId.parse_ranges()
ProductId.invalids(test_ranges)
  |> IO.inspect()
  |> Enum.sum()
input_ranges = File.read!(__DIR__ <> "/dec-02-input.txt")
  |> ProductId.parse_ranges()
ProductId.invalids(input_ranges)
  |> IO.inspect()
  |> Enum.sum()

Part Two

defmodule ProductId2 do
  def invalid?(id) do
    s = Integer.to_string(id)
    l = String.length(s)
    if l < 2 do
      false
    else
      Enum.map(1..div(l, 2), fn i ->
        if rem(l, i) != 0 do
          false
        else
          s0 = String.slice(s, 0..(i-1))
          s == String.duplicate(s0, div(l, i))
        end
      end)
        |> Enum.any?()
    end
  end
end
ProductId.invalids(test_ranges, &ProductId2.invalid?/1)
  |> IO.inspect()
  |> Enum.sum()
ProductId.invalids(input_ranges, &ProductId2.invalid?/1)
  |> IO.inspect()
  |> Enum.sum()
everte

everte

I’ve also gone for the brute-force option. Not very elegant and slow (3s or so), luckily the input didn’t make it impossible to just brute force it so I didn’t have to think hard about a clever solution (although I am intrigued and hope to see some clever ways here later today!).

aoc 2025, 2 do
  def p1(input) do
    input
    |> parse_input
    |> Enum.filter(&repeating_id?(&1))
    |> Enum.sum()
  end

  def p2(input) do
    input
    |> parse_input
    |> Enum.filter(&any_repeating_ids?(&1))
    |> Enum.sum()
  end

  defp repeating_id?(id) do
    digits = Integer.digits(id)
    {l, r} = Enum.split(digits, div(length(digits), 2))
    l == r
  end

  defp any_repeating_ids?(id) do
    digits = Integer.digits(id)
    half_len = div(length(digits), 2)

    for pair_len <- 1..half_len//1 do
      [first | _] = chunked_list = Enum.chunk_every(digits, pair_len)
      Enum.all?(chunked_list, fn element -> element == first end)
    end
    |> Enum.any?()
  end

  defp parse_input(input) do
    input
    |> String.split(",", trim: true)
    |> Enum.map(&String.split(&1, "-", trim: true))
    |> Enum.map(fn [l, r] ->
      l = String.to_integer(l)
      r = String.to_integer(r)
      Enum.to_list(l..r)
    end)
    |> List.flatten()
  end
end
lud

lud

Yes brute force is the way :smiley:

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

  def parse(input, _part) do
    input
    |> Input.read!()
    |> String.trim()
    |> String.split(",")
    |> Enum.map(fn range ->
      [left, right] = String.split(range, "-")
      left = String.to_integer(left)
      right = String.to_integer(right)
      left..right
    end)
  end

  def part_one(problem) do
    problem
    |> Stream.flat_map(& &1)
    |> Stream.filter(&invalid_p1?/1)
    |> Enum.sum()
  end

  defp invalid_p1?(n) do
    mirror?(Integer.digits(n))
  end

  defp mirror?([a, a]), do: true
  defp mirror?([a, b, a, b]), do: true
  defp mirror?([a, b, c, a, b, c]), do: true
  defp mirror?([a, b, c, d, a, b, c, d]), do: true
  defp mirror?([a, b, c, d, e, a, b, c, d, e]), do: true
  defp mirror?(_), do: false

  def part_two(problem) do
    problem
    |> Stream.flat_map(& &1)
    |> Stream.filter(&invalid_p2?/1)
    |> Enum.sum()
  end

  defp invalid_p2?(n) do
    repeats?(Integer.digits(n))
  end

  defp repeats?([a, a]), do: true
  defp repeats?([a, a, a]), do: true
  defp repeats?([a, b, a, b]), do: true
  defp repeats?([a, a, a, a, a]), do: true
  defp repeats?([a, b, a, b, a, b]), do: true
  defp repeats?([a, b, c, a, b, c]), do: true
  defp repeats?([a, a, a, a, a, a, a]), do: true
  defp repeats?([a, b, a, b, a, b, a, b]), do: true
  defp repeats?([a, b, c, d, a, b, c, d]), do: true
  defp repeats?([a, a, a, a, a, a, a, a, a]), do: true
  defp repeats?([a, b, c, a, b, c, a, b, c]), do: true
  defp repeats?([a, b, a, b, a, b, a, b, a, b]), do: true
  defp repeats?([a, b, c, d, e, a, b, c, d, e]), do: true
  defp repeats?(_), do: false
end

Edit: I realize now that mirror? is a very incorrect name for that function :smiley:

hauleth

hauleth

Today is brute day:

# Parse
ranges =
  puzzle_input
  |> String.trim()
  |> String.split(",")
  |> Enum.map(fn range ->
    range
    |> String.split("-")
    |> Enum.map(&String.to_integer/1)
    |> then(&apply(Range, :new, &1))
  end)

# Impl
defmodule ElfRanges do
  def valid?(num) do
    len = floor(:math.log10(num)) + 1

    valid_n?(num, len, 2)
  end

  def valid_any?(num) do
    len = floor(:math.log10(num)) + 1

    Enum.all?(2..len//1, &valid_n?(num, len, &1))
  end

  def valid_n?(num, len, n) do
    if rem(len, n) == 0 do
      step = 10 ** div(len, n)

      Stream.unfold(num, fn
        0 -> nil
        val -> {rem(val, step), div(val, step)}
      end)
      |> Enum.dedup()
      |> then(&(not match?([_], &1)))
    else
      true
    end
  end
end

# P1
ranges
|> Stream.flat_map(& &1)
|> Stream.reject(&ElfRanges.valid?/1)
|> Enum.sum()

# P2
ranges
|> Stream.flat_map(& &1)
|> Stream.reject(&ElfRanges.valid_any?/1)
|> Enum.sum()
mudasobwa

mudasobwa

Creator of Cure

No regexes, no magic save for metaprogramming.

  defmodule H do
    def seq(i, count) do
      List.duplicate(
        {:"::", [], [{:seq, [], Elixir}, {:-, [], [{:binary, [], nil}, {:size, [], [i]}]}]},
        count
      )
    end
  end

  defmodule Day2 do
    @input "day2_1.input" |> File.read!() |> String.trim()

    Enum.each(1..100, fn i ->
      def forged_1(<<seq::binary-size(unquote(i)), seq::binary-size(unquote(i))>>), do: 1
    end)

    def forged_1(_), do: 0

    import Aoc2025.H

    for count <- 2..12, i <- 1..100 do
      def forged_2(<<unquote_splicing(seq(i, count))>>), do: 1
    end

    def forged_2(_), do: 0

    def calc(input \\ @input) do
      input
      |> String.split(",", trim: true)
      |> Stream.map(&String.split(&1, "-", trim: true))
      |> Stream.map(fn [b, e] -> String.to_integer(b)..String.to_integer(e) end)
      |> Stream.map(fn range -> Enum.reduce(range, 0, &(&2 + &1 * forged_2("#{&1}"))) end)
      |> Enum.sum()
    end
  end
tnlogy

tnlogy

Nice to see alternative solutions, as a first time use of Tasks I expected my naive version with async_stream to be faster than my slow solution, but it’s even slower. Why is that?

defmodule Advent2025Test do
  use ExUnit.Case

  def day2_data() do
    "day2.txt"
    |> File.stream!()
    |> Stream.flat_map(fn line -> String.split(String.trim(line), ",") end)
    |> Stream.map(fn line -> String.split(line, "-") end)
    |> Stream.flat_map(fn [from, to] ->
      Stream.map(String.to_integer(from)..String.to_integer(to), fn n -> n end)
    end)
  end

  def repeating_twice?(n) do
    ns = Integer.to_string(n)
    hl = Integer.floor_div(String.length(ns), 2)

    rem(String.length(ns), 2) == 0 and
      String.slice(ns, 0..(hl - 1)) == String.slice(ns, -hl..-1)
  end

  def repeating_any?(n) do
    x = Integer.to_string(n)
    String.length(x) > 1 and
      Enum.any?(
        Stream.map(0..(String.length(x) - 2), fn i ->
          Enum.all?(Stream.map(String.split(x, String.slice(x, 0..i)), fn x -> x == "" end))
        end)
      )
  end

  test "day2_p1" do
    sum = Enum.sum(day2_data() |> Stream.filter(fn n -> repeating_twice?(n) end))
    IO.puts("answer #{sum}")
    assert sum == 18_952_700_150
  end

  test "day2_p2_1" do
    sum = Enum.sum(day2_data() |> Stream.filter(fn n -> repeating_any?(n) end))
    IO.puts("answer #{sum}")
  end

  test "day2_p2" do
    res = day2_data() |> Task.async_stream(fn n -> if repeating_any?(n) do n else 0 end end)
    sum = Enum.sum_by(res, fn {:ok, num} -> num end)
    IO.puts("answer #{sum}")
  end
end
sevenseacat

sevenseacat

Author of Ash Framework

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

I did this for part 1 too, but converted it to Integer.digits(num) and then chunking the resulting list into different sizes. Totally brute force but with some async goodies, part 2 runs in 0.4 seconds on my M1.

rvnash

rvnash

I guess I did the brute force way too, as part 2 takes about 3 seconds. Who here has the more efficient solution? I can’t imagine what that might be.

defmodule RAoc.Solutions.Y25.Day02 do
  alias AoC.Input
  require Integer

  def parse(input, _part) do
    Input.read!(input)
    |> String.trim()
    |> String.split(",")
    |> Enum.map(&String.split(&1, "-"))
    |> Enum.map(fn [str1, str2] -> String.to_integer(str1)..String.to_integer(str2) end)
    |> Stream.flat_map(fn range -> range end)
  end

  def part_one(problem) do
    problem
    |> Stream.filter(&is_invalid_code_part1?/1)
    |> Enum.sum()
  end

  defp is_invalid_code_part1?(code) do
    str_code = Integer.to_string(code)
    len = String.length(str_code)
    (Integer.is_even(len) && is_repeated_pattern?(str_code, len, 2)) || false
  end

  def part_two(problem) do
    problem
    |> Stream.filter(&is_invalid_code_part2?/1)
    |> Enum.sum()
  end

  defp is_invalid_code_part2?(code) do
    str_code = Integer.to_string(code)
    len = String.length(str_code)

    Enum.filter(2..len//1, fn n -> is_divisible?(len, n) end)
    |> Enum.any?(fn num_parts ->
      is_repeated_pattern?(str_code, len, num_parts)
    end)
  end

  # Because of previous checks, len is divisible by num_parts, this function doesn't check that
  defp is_repeated_pattern?(str_code, len, num_parts) do
    split_into_parts(str_code, [], div(len, num_parts))
    |> Enum.uniq()
    |> Enum.count() == 1
  end

  defp is_divisible?(n, m), do: div(n, m) * m == n

  defp split_into_parts("", acc, _at) do
    acc
  end

  defp split_into_parts(str, acc, at) do
    {str1, str2} = String.split_at(str, at)
    split_into_parts(str2, [str1 | acc], at)
  end
end
rvnash

rvnash

Yup, this is way faster than mine!

Edit: This is a great example of how fast and efficient pattern matching must be inside the BEAM.

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