2024 AoC Day 9 – Disk Fragmenter

This is a solution to Advent of Code 2023 day 9, written in Raku.

https://adventofcode.com/2023/day/9

Part One

The amphipod would like to move file blocks one at a time from the end of the disk to the leftmost free space block (until there are no gaps remaining between file blocks).

Compact the amphipod's hard drive using the process he requested. What is the resulting filesystem checksum?

use Test;

sub day-nine($file, :$debug = False) {
    my @digits = $file.IO.slurp.chomp.comb;
    my $id = 0;
    my $is-file = True;

    my @disk = @digits.map(
        -> $d {
            LEAVE $is-file = !$is-file;
            if $is-file {
                LEAVE $id += 1;
                $id xx $d
            } else {
                '.' xx $d
            }
        }
    ).flat;
    say @disk.join if $debug;

    for 0..* -> $i {
        @disk.pop while @disk[*-1] eq '.';
        last if $i >= +@disk;
        if @disk[$i] eq '.' {
            @disk[$i] = @disk.pop;
        }
    }

    say @disk.join if $debug;
    [+] @disk.kv.map(-> $k, $v { $k * $v });
}

say day-nine('test-9.txt', :debug);
is day-nine('test-9.txt'), 1928, 'example input';
say day-nine('input-9.txt');
00...111...2...333.44.5555.6666.777.888899
0099811188827773336446555566
1928
ok 1 - example input
6415184586041

Part Two

The eager amphipod already has a new plan: rather than move individual blocks, he'd like to try compacting the files on his disk by moving whole files instead…

Start over, now compacting the amphipod's hard drive using this new method instead. What is the resulting filesystem checksum?

use Test;

sub day-nine($file, :$debug = False) {
    my @digits = $file.IO.slurp.chomp.comb;
    my $id = 0;
    my $is-file = True;

    my %file-spans;
    my @free-spans;

    my $pos = 0;
    my @disk = @digits.map(
        -> $d {
            LEAVE $is-file = !$is-file;
            LEAVE $pos += $d;
            if $is-file {
                LEAVE $id += 1;
                %file-spans{$id} = $pos..^$pos+$d;
                $id xx $d
            } else {
                @free-spans.push: $pos..^$pos+$d;
                '.' xx $d
            }
        }
    ).flat;
    say @disk.join if $debug;

    for $id ^... 0 -> $n {
        for ^+@free-spans -> $i {
            my $span = @free-spans[$i];
            my $file = %file-spans{$n};
            last if $file.min < $span.min;

            if +$file <= +$span {
                my $pos = $span.min;
                $file.list.map(
                    -> $f {
                        @disk[$pos] = @disk[$f];
                        @disk[$f] = '.';
                        $pos += 1;
                    });
                @free-spans[$i] = $pos ..^ $span.max;
                last;
            }
        }
    }

    say @disk.join if $debug;
    [+] @disk.kv.map(-> $k, $v { $k * $v if $v ne '.' });
}

say day-nine('test-9.txt', :debug);
is day-nine('test-9.txt'), 2858, 'example input';
say day-nine('input-9.txt');
say "Took " ~ (now - ENTER now).base(10, 2) ~ "s";
00...111...2...333.44.5555.6666.777.888899
00992111777.44.333....5555.6666.....8888..
2858
ok 1 - example input
6436819084274
Took 25.67s
raku