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

dominicletz
Hey there, I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first. ...
New
raa-ziq
Hi everyone :waving_hand: I wanted to share something I’ve been building — NowBlind, a random chat app designed to help people discover ...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
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

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews