defmodule Day2 do
def file, do: Parser.read_file(2)
def test, do: Parser.read_file("test")
def parse(input) do
input
|> Enum.join()
|> String.split(",")
|> Enum.map(&(&1 |> String.split("-") |> Enum.map(fn a -> String.to_integer(a) end)))
end
def solve(input \\ file()) do
input
|> parse()
|> Enum.reduce([], fn range, acc ->
range
|> find_invalid_ids_in_range(&is_symmetric?/1)
|> Enum.concat(acc)
end)
|> Enum.sum()
end
def solve_two(input \\ file()) do
input
|> parse()
|> Enum.reduce([], fn range, acc ->
range
|> find_invalid_ids_in_range(&contain_pattern?/1)
|> Enum.concat(acc)
end)
|> Enum.sum()
end
def find_invalid_ids_in_range([alpha, omega], filter_function) do
Enum.filter(alpha..omega, &filter_function.(&1))
end
def is_symmetric?(number) do
string = Integer.to_string(number)
length = String.length(string)
{a, b} = String.split_at(string, (length / 2) |> trunc())
a == b
end
def contain_pattern?(number) do
string = Integer.to_string(number)
length = String.length(string)
if length == 1 do
false
else
divide_by = Enum.filter(2..length, fn a -> rem(length, a) == 0 end)
Enum.any?(divide_by, &is_invalid_multiple(string, length, &1))
end
end
def is_invalid_multiple(string, length, divide_by) do
slice_to = trunc(length / divide_by) - 1
pattern = string |> String.slice(0..slice_to)
String.duplicate(pattern, divide_by) == string
end
end
#!/usr/bin/env elixir
# Advent of Code 2025. Day 2
Mix.install([
{:nimble_parsec, "~> 1.4.2"},
])
defmodule M do
import NimbleParsec
ranges = repeat(
integer(min: 1)
|> ignore(string("-"))
|> integer(min: 1)
|> tag(:range)
|> optional(ignore(string(",")))
)
defparsec :ranges, ranges
def invalid1(x) do
x = to_string(x)
{a,b} = String.split_at(x, div(String.length(x),2))
a == b
end
def invalid2(x) do
String.match?(to_string(x), ~r/^(\d+?)\1+$/)
end
end
ranges = File.read!("../day02.txt")
|> M.ranges()
|> then(fn {:ok, lst, "\n", %{}, _, _} -> Enum.map(lst, fn {:range, [lo, hi]} -> (lo..hi) end) end)
# Part 1
ranges
|> Enum.flat_map(fn range -> Enum.filter(range, &M.invalid1/1) end)
|> Enum.sum()
|> IO.inspect(label: "Day 02. Part 1")
# Part 2
ranges
|> Enum.flat_map(fn range -> Enum.filter(range, &M.invalid2/1) end)
|> Enum.sum()
|> IO.inspect(label: "Day 02. Part 2")
Probably the most interesting thing I did differently was using binary comprehensions to split the string into equal parts:
def repeats?(string, parts \\ 2)
def repeats?(string, parts) when parts > byte_size(string), do: false
def repeats?(string, parts) do
repeats? =
if rem(byte_size(string), parts) > 0 do
# Can't repeat if not divisible
false
else
part_len = div(byte_size(string), parts)
parts = for <<part::binary-size(part_len) <- string>>, do: part
parts |> Enum.uniq() |> length() == 1
end
repeats? or repeats?(string, parts + 1)
end
Finally getting back to the puzzles…
defmodule Day02 do
def part1(file), do: main(file, &invalid1?/1)
def part2(file), do: main(file, &invalid2?/1)
def main(file, filter) do
rs = file |> File.read!() |> String.replace("\n", "") |> String.split(",") |> Enum.map(&rng/1)
rs
|> Enum.flat_map(fn range -> Enum.filter(range, &(&1 |> Integer.digits() |> filter.())) end)
|> Enum.sum()
end
def rng(string) do
[first, last] = string |> String.split("-") |> Enum.map(&(&1 |> Integer.parse() |> elem(0)))
Range.new(first, last)
end
def invalid1?(digits) do
len = length(digits)
rem(len, 2) == 0 and repeats?(digits, div(len, 2))
end
def invalid2?(digits) do
len = length(digits)
len > 1 and Enum.any?(1..max(div(len, 2), 1), &(rem(len, &1) == 0 and repeats?(digits, &1)))
end
def repeats?(digits, size) do
[chunk | rest] = Enum.chunk_every(digits, size)
Enum.all?(rest, &(&1 == chunk))
end
end






















