Day 9: Disk Fragmenter
For Part 1, we can solve this without actually defragmenting. We loop through the input from two sides.
function checksum(input::Vector{I}) where {I <: Integer}
<<day09-checksum>>
end
We keep track of the block_id
and
total
,
= 0
block_id = 0 total
Then define a helper function to increase the checksum:
function add(fid::Int64, fsize::I)
+= (block_id * fsize + ((fsize - 1) * fsize) ÷ 2) * fid
total += fsize
block_id end
Now we loop ever the input, alternating between file blocks and free blocks.
= length(input) ÷ 2
tail_file_id = input[end]
tail_file_size
for i in 1:length(input)
if (i % 2 == 1)
<<day09-file>>
else
<<day09-free-space>>
end
end
In case of a file, we need to check that we’re not past the files that we already allocated to previous free space.
= i ÷ 2
file_id if file_id >= tail_file_id
add(file_id, tail_file_size)
return total
end
add(file_id, input[i])
If we’re in free space, we add from the tail.
= input[i]
gap
while gap > tail_file_size
add(tail_file_id, tail_file_size)
-= tail_file_size
gap -= 1
tail_file_id if tail_file_id == i ÷ 2 - 1
return total
end
= input[tail_file_id*2 + 1]
tail_file_size end
add(tail_file_id, gap)
-= gap tail_file_size
This entire algorithm is quite finicky and sensitive to off-by-one errors, but it seems to work.
Part 2
For Part 2, I’m afraid we actually have to keep a data structure. I’m
encoding the disk as a vector of (id, len)
pairs, where an
id of -1
corresponds to free space. I’m looping down from
high file id, each time using findprev
to find the position
and length of that file. Then findfirst
to find the
earliest gap that fits the file.
run_length(input::Vector{Int}) =
zip(flatten(zip(countfrom(0), repeated(-1))), input) |> collect
function defrag!(rl::Vector{Tuple{Int, Int}})
= rl[end][1]
max_file_id = length(rl)
ptr for file_id = max_file_id:-1:0
# update ptr and file_size to entry matching file_id
= findprev(((id, _),)->id == file_id, rl, ptr)
ptr === nothing && throw("could not find $(file_id) in $(rl)")
ptr = rl[ptr][2]
file_size
# find a gap large enough
= findfirst(
free_block ->id == -1 && sz >= file_size,
((id, sz),)@view rl[1:ptr-1])
=== nothing && continue
free_block
= rl[free_block][2]
free_size = rl[ptr]
rl[free_block] = (-1, file_size)
rl[ptr] == free_size && continue
file_size insert!(rl, free_block + 1, (-1, free_size - file_size))
end
return rl
end
function checksum_2(rl::Vector{Tuple{Int, Int}})
= 0
total = 0
px for (i, x) in rl
if i >= 0
+= (px * x + ((x-1) * x) ÷ 2) * i
total end
+= x
px end
return total
end
Main
module Day09
using .Iterators: zip, flatten, countfrom, repeated
<<day09-part1>>
<<day09-part2>>
function main(io::IO)
= collect(strip(read(io, String))) .- '0'
input = checksum(input)
part1 = run_length(input)
rl defrag!(rl)
= checksum_2(rl)
part2 return part1, part2
end
end
# add tests