using System.Collections.Frozen; using System.Numerics; using Spectre.Console; namespace AdventOfCode.Days; public class Day9 : Day { public override int Number => 9; public override string Name => "Disk Fragmenter"; public override void RunPart1(bool display = true) { var (blocks, freeBlocksStarts) = ParseDiskBlocks(); var nextFreeIndex = freeBlocksStarts.Dequeue(); for (var i = blocks.Count - 1; i >= 0; i--) { // There is no more available space to move blocks into on the left if (nextFreeIndex >= i) { break; } if (blocks[i] is -1) { continue; } blocks[nextFreeIndex] = blocks[i]; blocks[i] = -1; nextFreeIndex++; // Go to next free blocks if current free block is full if (blocks[nextFreeIndex] is not -1) { nextFreeIndex = freeBlocksStarts.Dequeue(); } } var checksum = blocks .Select((block, index) => block is -1 ? 0 : (long)block * index) .Sum(); if (display) { AnsiConsole.MarkupLine($"[green]Filesystem checksum: [yellow]{checksum}[/][/]"); } } public override void RunPart2(bool display = true) { var (blocks, freeBlocks) = ParseDiskBlocks2(); var mapStart = string.Join(" ", blocks); for (var i = blocks.Count - 1; i >= 0; i--) { if (blocks[i] is -1) { continue; } // Get file length var fileId = blocks[i]; var endPosition = i; var startPosition = i; while (startPosition > 0 && blocks[startPosition - 1] == fileId) { startPosition--; } var length = (endPosition - startPosition) + 1; // Find first available free block on the left var freeBlockIndex = freeBlocks.FindIndex(b => b.StartIndex < startPosition && b.Length >= length); if (freeBlockIndex is not -1) { var freeBlock = freeBlocks[freeBlockIndex]; for (var offset = 0; offset < length; offset++) { blocks[freeBlock.StartIndex + offset] = blocks[startPosition + offset]; blocks[startPosition + offset] = -1; } // Update free block start and length freeBlocks[freeBlockIndex] = (freeBlock.StartIndex + length, freeBlock.Length - length); } // Move to next file or free block i = startPosition; } var mapEnd = string.Join(" ", blocks).Replace("-1", "."); var checksum = blocks .Select((block, index) => block is -1 ? 0 : (long)block * index) .Sum(); if (display) { AnsiConsole.MarkupLine($"[green]Filesystem checksum: [yellow]{checksum}[/][/]"); } } private (List Blocks, Queue FreeBlocksStarts) ParseDiskBlocks() { var blocks = new List(20_000 * 10); var freeBlocksStarts = new Queue(); var readingBlock = true; var blockId = 0; foreach (var length in Input.Select(encoded => encoded - '0')) { if (readingBlock) { for (var i = 0; i < length; i++) { blocks.Add(blockId); } blockId++; } else if (length > 0) { freeBlocksStarts.Enqueue(blocks.Count); for (var i = 0; i < length; i++) { blocks.Add(-1); } } readingBlock = !readingBlock; } return (blocks, freeBlocksStarts); } private (List Blocks, List<(int StartIndex, int Length)> FreeBlocks) ParseDiskBlocks2() { var blocks = new List(20_000 * 10); var freeBlocksStarts = new List<(int StartIndex, int Length)>(); var readingBlock = true; var blockId = 0; foreach (var length in Input.Select(encoded => encoded - '0')) { if (readingBlock) { for (var i = 0; i < length; i++) { blocks.Add(blockId); } blockId++; } else if (length > 0) { freeBlocksStarts.Add((blocks.Count, length)); for (var i = 0; i < length; i++) { blocks.Add(-1); } } readingBlock = !readingBlock; } return (blocks, freeBlocksStarts); } }