Powered by AppSignal & Oban Pro

--- Day 2: Gift Shop ---

2025/day_2.livemd

— Day 2: Gift Shop —

Mix.install([{:kino_aoc, "~> 0.1"}])

Part 1

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2025", "2", System.fetch_env!("LB_AOC_SESSION_COOKIE"))
test_input = "11-22,95-115,998-1012,1188511880-1188511890,222220-222224,1698522-1698528,446443-446449,38593856-38593862,565653-565659,824824821-824824827,2121212118-2121212124"
defmodule GiftShop do
  def part_1(input) do
    ranges = parse_ranges(input)

    ranges
    |> Enum.reduce([], fn range, acc ->
      Enum.reduce(range, acc, fn id, acc ->
        if id_invalid?(id) do
          [id | acc]
        else
          acc
        end
      end)
    end)
    |> Enum.sum()
  end

  def part_2(input) do
    ranges = parse_ranges(input)
    max_chunk_size = max_chunk_size(ranges)

    ranges
    |> Enum.reduce([], fn range, acc ->
      Enum.reduce(range, acc, fn id, acc ->
        if id_invalid?(id, max_chunk_size) do
          [id | acc]
        else
          acc
        end
      end)
    end)
    |> Enum.sum()
  end

  defp parse_ranges(input) do
    input
    |> String.split(",")
    |> Enum.map(&create_range/1)
  end

  defp max_chunk_size(ranges) do
    ranges
    |> Enum.map(fn range ->
      range.last |> Integer.digits() |> length() |> div(2)
    end)
    |> Enum.max()
  end

  defp create_range(range_string) do
    {range_start, "-" <> range_end_string} = Integer.parse(range_string)
    {range_end, _} = Integer.parse(range_end_string)
    range_start..range_end
  end

  defp id_invalid?(id) when id <= 10, do: false

  defp id_invalid?(id) do
    digits = Integer.digits(id)
    length = length(digits)

    if rem(length, 2) != 0 do
      false
    else
      {first, second} = Enum.split(digits, div(length, 2))
      first == second
    end
  end

  defp id_invalid?(id, _max_chunk_szie) when id <= 10, do: false

  defp id_invalid?(id, max_chunk_size) do
    [first | _rest] = digits = Integer.digits(id)

    Enum.all?(digits, &amp;(&amp;1 == first)) || has_repeating_digits?(digits, max_chunk_size)
  end

  defp has_repeating_digits?(id_digits, max_chunk_size) do
    Enum.reduce_while(2..max_chunk_size, false, fn
      chunk_size, acc when length(id_digits) <= chunk_size ->
        {:cont, acc}

      chunk_size, acc ->
        [first_chunk | _rest] = chunks = Enum.chunk_every(id_digits, chunk_size)

        if Enum.all?(chunks, &amp;(&amp;1 == first_chunk)) do
          {:halt, true}
        else
          {:cont, acc}
        end
    end)
  end
end
# GiftShop.part_1(test_input)
GiftShop.part_1(puzzle_input)

Part 2

# GiftShop.part_2(test_input)
GiftShop.part_2(puzzle_input)