Day 9: Disk Fragmenter
Megathread guidelines
- Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
- You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL
FAQ
- What is this?: Here is a post with a large amount of details: https://programming.dev/post/6637268
- Where do I participate?: https://adventofcode.com/
- Is there a leaderboard for the community?: We have a programming.dev leaderboard with the info on how to join in this post: https://programming.dev/post/6631465
python
solution
import aoc
def setup():
dm = [int(x) for x in aoc.get_lines(9, stripped=True)[0]]
ldm = len(dm)
d = []
f = 0
for i in range(0, ldm, 2):
lfi = dm[i]
d.extend([f] * lfi)
f += 1
if i + 1 < ldm:
lfr = dm[i + 1]
d.extend([-1] * lfr)
return d
def one():
d = setup()
h = 0
t = len(d) - 1
while h < t:
if d[h] == -1:
while t > h and d[t] == -1:
t -= 1
if t > h:
d[h], d[t] = d[t], d[h]
t -= 1
h += 1
print(sum(i * v for i, v in enumerate(d) if v != -1))
def two():
d = setup()
md = max(d)
for fid in range(md, -1, -1):
fis = [i for i, v in enumerate(d) if v == fid]
if not fis:
continue
s, e = fis[0], fis[-1] + 1
l, f, fi = e - s, 0, None
for i in range(s):
if d[i] == -1:
if f == 0:
fi = i
f += 1
if f == l:
break
else:
f, fi = 0, None
if fi is not None and f == l:
d[fi:fi+l] = [fid]*l
d[s:e] = [-1]*l
print(sum(i * v for i, v in enumerate(d) if v != -1))
one()
two()
C#
public class Day09 : Solver
{
private string data;
public void Presolve(string input) {
data = input.Trim();
}
public string SolveFirst() {
var arr = new List<int>();
bool file = true;
int file_id = 0;
foreach (var ch in data) {
if (file) {
Enumerable.Range(0, ch - '0').ToList().ForEach(_ => arr.Add(file_id));
file_id++;
} else {
Enumerable.Range(0, ch - '0').ToList().ForEach(_ => arr.Add(-1));
}
file = !file;
}
int from_ptr = arr.Count - 1;
int to_ptr = 0;
while (from_ptr > to_ptr) {
if (arr[to_ptr] != -1) {
to_ptr++;
continue;
}
if (arr[from_ptr] == -1) {
from_ptr--;
continue;
}
arr[to_ptr] = arr[from_ptr];
arr[from_ptr] = -1;
to_ptr++;
from_ptr--;
}
return Enumerable.Range(0, arr.Count)
.Select(block_id => arr[block_id] > 0 ? ((long)arr[block_id]) * block_id : 0)
.Sum().ToString();
}
public string SolveSecond() {
var files = new List<(int Start, int Size, int Id)>();
bool is_file = true;
int file_id = 0;
int block_id = 0;
foreach (var ch in data) {
if (is_file) {
files.Add((block_id, ch - '0', file_id));
file_id++;
}
is_file = !is_file;
block_id += (ch - '0');
}
while (true) {
bool moved = false;
for (int from_ptr = files.Count - 1; from_ptr >= 1; from_ptr--) {
var file = files[from_ptr];
if (file.Id >= file_id) continue;
file_id = file.Id;
for (int to_ptr = 0; to_ptr < from_ptr; to_ptr++) {
if (files[to_ptr + 1].Start - files[to_ptr].Start - files[to_ptr].Size >= file.Size) {
files.RemoveAt(from_ptr);
files.Insert(to_ptr + 1, file with { Start = files[to_ptr].Start + files[to_ptr].Size });
moved = true;
break;
}
}
if (moved) break;
}
if (!moved) break;
}
return files.Select(file => ((long)file.Id) * file.Size * (2 * ((long)file.Start) + file.Size - 1) / 2)
.Sum().ToString();
}
}
Haskell
Not a lot of time to come up with a pretty solution today; sorry.
Ugly first solution
import Data.List
import Data.Maybe
import Data.Sequence (Seq)
import Data.Sequence qualified as Seq
readInput :: String -> Seq (Maybe Int, Int)
readInput =
Seq.fromList
. zip (intersperse Nothing $ map Just [0 ..])
. (map (read . singleton) . head . lines)
expand :: Seq (Maybe Int, Int) -> [Maybe Int]
expand = concatMap (uncurry $ flip replicate)
compact :: Seq (Maybe Int, Int) -> Seq (Maybe Int, Int)
compact chunks =
case Seq.spanr (isNothing . fst) chunks of
(suffix, Seq.Empty) -> suffix
(suffix, chunks' Seq.:|> file@(_, fileSize)) ->
case Seq.breakl (\(id, size) -> isNothing id && size >= fileSize) chunks' of
(_, Seq.Empty) -> compact chunks' Seq.>< file Seq.<| suffix
(prefix, (Nothing, gapSize) Seq.:<| chunks'') ->
compact $ prefix Seq.>< file Seq.<| (Nothing, gapSize - fileSize) Seq.<| chunks'' Seq.>< (Nothing, fileSize) Seq.<| suffix
part1, part2 :: Seq (Maybe Int, Int) -> Int
part1 input =
let blocks = dropWhileEnd isNothing $ expand input
files = catMaybes blocks
space = length blocks - length files
compacted = take (length files) $ fill blocks (reverse files)
in sum $ zipWith (*) [0 ..] compacted
where
fill (Nothing : xs) (y : ys) = y : fill xs ys
fill (Just x : xs) ys = x : fill xs ys
part2 = sum . zipWith (\i id -> maybe 0 (* i) id) [0 ..] . expand . compact
main = do
input <- readInput <$> readFile "input09"
print $ part1 input
print $ part2 input
Second attempt! I like this one much better.
Edit: down to 0.040 secs now!
import Control.Arrow
import Data.Either
import Data.List
import Data.Map (Map)
import Data.Map qualified as Map
type Layout = ([(Int, (Int, Int))], Map Int Int)
readInput :: String -> Layout
readInput =
map (read . singleton) . head . lines
>>> (scanl' (+) 0 >>= zip) -- list of (pos, len)
>>> zipWith ($) (intersperse Right [Left . (id,) | id <- [0 ..]])
>>> partitionEithers
>>> filter ((> 0) . snd . snd) *** Map.filter (> 0) . Map.fromAscList
checksum :: Layout -> Int
checksum = sum . map (\(id, (pos, len)) -> id * len * (2 * pos + len - 1) `div` 2) . fst
compact :: (Int -> Int -> Bool) -> Layout -> Layout
compact select (files, spaces) = foldr moveFile ([], spaces) files
where
moveFile file@(fileId, (filePos, fileLen)) (files, spaces) =
let candidates = Map.assocs $ fst . Map.split filePos $ spaces
in case find (select fileLen . snd) candidates of
Just (spacePos, spaceLen) ->
let spaces' = Map.delete spacePos spaces
in if spaceLen >= fileLen
then
( (fileId, (spacePos, fileLen)) : files,
if spaceLen == fileLen
then spaces'
else Map.insert (spacePos + fileLen) (spaceLen - fileLen) spaces'
)
else
moveFile
(fileId, (filePos + spaceLen, fileLen - spaceLen))
((fileId, (spacePos, spaceLen)) : files, spaces')
Nothing -> (file : files, spaces)
main = do
input <- readInput <$> readFile "input09"
mapM_ (print . checksum . ($ input) . compact) [const $ const True, (<=)]
It will always be a wonder to me how you manage to do so much in so few lines, even your naive solution only takes a few seconds to run. 🤯
Aww, thank you <3
It’s just practice, I guess? (The maths degree probably doesn’t hurt either)
Was really blanking on how to do this one nicely, so a bunch of stacked loops it is…
Also ended up writing two separate solutions for the first and second part, since I couldn’t get acceptable performance otherwise. Still takes half a second on my machine, mainly on the second part.
This is technically the second implementation, the first one took minutes to calculate, so I wasn’t really okay with stamping it as my solution-of-choice.
Can definitely still be improved, but I’ve been poking and prodding at this code for hours on end now, so it’s long past time to let it sit for a while and see if I get any better ideas later.
C#
int[] layout = new int[0];
public void Input(IEnumerable<string> lines)
{
layout = string.Join("", lines).ToCharArray().Select(c => int.Parse(c.ToString())).ToArray();
}
public void Part1()
{
ushort?[] blocks = BuildBlockmap().ToArray();
var it = 0;
for (var i = blocks.Length - 1; i > it; i--)
{
if (blocks[i] == null)
continue;
while (it < blocks.Length && blocks[it] != null)
++it;
if (it >= blocks.Length)
break;
(blocks[it], blocks[i]) = (blocks[i], null);
}
long checksum = 0;
foreach (var part in blocks.OfType<ushort>().Select((b, i) => i * b))
checksum += part;
Console.WriteLine($"Checksum: {checksum}");
}
public void Part2()
{
var sparse = BuildSparsemap().ToList();
for (var i = sparse.Count - 1; i >= 0; i--)
{
if (sparse[i].Item1 == null)
continue;
for (var j = 0; j < i; ++j)
{
if (sparse[j].Item1 != null)
continue;
if (sparse[i].Item2 > sparse[j].Item2)
continue;
var size = sparse[j].Item2;
size -= sparse[i].Item2;
(sparse[j], sparse[i]) = (sparse[i], (null, sparse[i].Item2));
if (i + 1 < sparse.Count && sparse[i + 1].Item1 == null)
{
sparse[i] = (null, (ushort)(sparse[i].Item2 + sparse[i + 1].Item2));
sparse.RemoveAt(i + 1);
}
if (sparse[i - 1].Item1 == null)
{
sparse[i - 1] = (null, (ushort)(sparse[i - 1].Item2 + sparse[i].Item2));
sparse.RemoveAt(i);
}
if (size > 0)
sparse.Insert(j + 1, (null, size));
j = i + 1;
}
}
int ind = 0;
long checksum = 0;
foreach (var (val, cnt) in sparse)
for (var i = 0; i < cnt; ++i)
{
checksum += (val ?? 0) * ind;
++ind;
}
Console.WriteLine($"Checksum: {checksum}");
}
IEnumerable<ushort?> BuildBlockmap()
{
ushort blockit = 0;
bool block = true;
foreach (var value in layout)
{
for (int i = 0; i < value; ++i)
yield return block ? blockit : null;
if (block)
blockit++;
block = !block;
}
}
IEnumerable<(ushort?, ushort)> BuildSparsemap()
{
ushort blockit = 0;
bool block = true;
foreach (var value in layout)
{
if (block)
yield return (blockit++, (ushort)value);
else
yield return (null, (ushort)value);
block = !block;
}
}