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"))
{:ok,
 "5959566378-5959623425,946263-1041590,7777713106-7777870316,35289387-35394603,400-605,9398763-9592164,74280544-74442206,85684682-85865536,90493-179243,202820-342465,872920-935940,76905692-76973065,822774704-822842541,642605-677786,3759067960-3759239836,1284-3164,755464-833196,52-128,3-14,30481-55388,844722790-844967944,83826709-83860070,9595933151-9595993435,4216-9667,529939-579900,1077949-1151438,394508-486310,794-1154,10159-17642,5471119-5683923,16-36,17797-29079,187-382"}
test_input = "11-22,95-115,998-1012,1188511880-1188511890,222220-222224,1698522-1698528,446443-446449,38593856-38593862,565653-565659,824824821-824824827,2121212118-2121212124"
"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
{:module, GiftShop, <<70, 79, 82, 49, 0, 0, 23, ...>>, {:has_repeating_digits?, 2}}
# GiftShop.part_1(test_input)
GiftShop.part_1(puzzle_input)
19605500130

Part 2

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