From d39987c5c45808e94aa5d6eb05b53110308abf01 Mon Sep 17 00:00:00 2001 From: Rebecca Le Date: Wed, 11 Dec 2024 17:32:36 +0800 Subject: [PATCH] 2024 day 9 - simplify with a map approach It works better on the evil inputs listed here - https://www.reddit.com/r/adventofcode/comments/1haauty/2024_day_9_part_2_bonus_test_case_that_might_make/ The first input goes from 3.2 to 1.1 seconds The second *really* evil input goes from 32 to 20 seconds The slowness is probably from sorting the gap lists, now --- lib/y2024/README.md | 4 +-- lib/y2024/day09.ex | 88 ++++++++++++++++++++++++++++----------------- 2 files changed, 58 insertions(+), 34 deletions(-) diff --git a/lib/y2024/README.md b/lib/y2024/README.md index 85bf666..a9fd93d 100644 --- a/lib/y2024/README.md +++ b/lib/y2024/README.md @@ -30,8 +30,8 @@ day 07, part 1 102.28 9.78 ms ±2.20% 9.77 ms 10. day 07, part 2 14.56 68.66 ms ±0.66% 68.75 ms 69.78 ms day 08, part 1 1060.88 0.94 ms ±6.65% 0.92 ms 1.08 ms day 08, part 2 827.26 1.21 ms ±7.61% 1.18 ms 1.39 ms -day 09, part 1 8.72 114.69 ms ±1.28% 114.98 ms 117.37 ms -day 09, part 2 7.25 137.96 ms ±0.69% 137.79 ms 141.44 ms +day 09, part 1 9.33 107.22 ms ±0.49% 107.06 ms 108.74 ms +day 09, part 2 7.68 130.17 ms ±0.93% 130.17 ms 132.76 ms day 10, part 1 3.87 258.13 ms ±0.90% 258.51 ms 263.14 ms day 10, part 2 4.19 238.57 ms ±0.55% 238.51 ms 241.13 ms day 11, part 1 179.79 5.56 ms ±6.00% 5.62 ms 6.21 ms diff --git a/lib/y2024/day09.ex b/lib/y2024/day09.ex index 27e83c1..a321991 100644 --- a/lib/y2024/day09.ex +++ b/lib/y2024/day09.ex @@ -61,9 +61,13 @@ defmodule Y2024.Day09 do {to_move, gaps} = Enum.split_with(list, &(&1.type == :fill)) disk = Map.new(list, &{&1.file_id, &1}) - gaps = Enum.reverse(gaps) - defrag(to_move, gaps, [], disk) + gap_map = + gaps + |> Enum.reverse() + |> Enum.group_by(& &1.size) + + defrag(to_move, gap_map, disk) |> Map.values() |> Enum.sort_by(& &1.start_at) |> Enum.reduce(0, fn file, acc -> @@ -73,43 +77,63 @@ defmodule Y2024.Day09 do end) end - defp defrag([], _, _, disk), do: disk + defp defrag([], _, disk), do: disk - defp defrag([_head | tail], [], gaps, disk), do: defrag(tail, Enum.reverse(gaps), [], disk) + defp defrag([head | tail], gap_map, disk) do + if gap = find_valid_gap(gap_map, head) do + # IO.puts("moving #{head.file_id} to #{gap.start_at}") + leftover_gap_size = gap.size - head.size - defp defrag([head | tail], [head_gap | tail_gaps], start_gaps, disk) do - if head.start_at < head_gap.start_at do - # IO.puts("can't move #{head.file_id}") - defrag(tail, Enum.reverse(start_gaps) ++ [head_gap | tail_gaps], [], disk) - else - if head_gap.size >= head.size do - # IO.puts("gonna move #{head.file_id} to #{head_gap.start_at}") - leftover_gap_size = head_gap.size - head.size - - gaps = - if leftover_gap_size > 0 do - new_gap = - head_gap - |> Map.put(:size, leftover_gap_size) - |> Map.put(:start_at, head_gap.start_at + head.size) - - Enum.reverse(start_gaps) ++ [new_gap | tail_gaps] - else - Enum.reverse(start_gaps) ++ tail_gaps - end - - disk = - Map.update!(disk, head.file_id, fn head -> - %{head | start_at: head_gap.start_at} + gap_map = + if leftover_gap_size > 0 do + # IO.puts("adding new gap of size #{leftover_gap_size} at #{gap.start_at + head.size}") + + new_gap = + gap + |> Map.put(:size, leftover_gap_size) + |> Map.put(:start_at, gap.start_at + head.size) + + gap_map + |> Map.update(new_gap.size, [new_gap], fn list -> + [new_gap | list] + |> Enum.sort_by(& &1.start_at) end) + else + gap_map + end + |> Map.update!(gap.size, fn list -> tl(list) end) - defrag(tail, gaps, [], disk) - else - defrag([head | tail], tail_gaps, [head_gap | start_gaps], disk) - end + disk = + Map.update!(disk, head.file_id, fn head -> + %{head | start_at: gap.start_at} + end) + + defrag(tail, gap_map, disk) + else + # IO.puts("can't move #{head.file_id}") + defrag(tail, gap_map, disk) end end + defp find_valid_gap(gap_map, %{size: min_size, start_at: max_start_at}) do + # IO.puts("looking for gap of size #{min_size}") + + Enum.reduce(gap_map, nil, fn + {_gap_size, []}, acc -> + acc + + {gap_size, gaps}, acc -> + gap = hd(gaps) + + if gap_size >= min_size && gap.start_at < max_start_at && + (!acc || acc.start_at > gap.start_at) do + gap + else + acc + end + end) + end + def parse_input(input) do input |> String.trim()