using System; using System.Collections.Generic; using System.Linq; using System.Reflection.Emit; using System.Text; using System.Threading.Tasks; using System.Text.RegularExpressions; using System.ComponentModel; using System.Data; namespace Advent24 { internal class Day9 { public Day9() { String fileData = System.IO.File.ReadAllText(@"..\..\..\inputd9.txt"); Int128 checksumWholeFile = 0; Int64 checksum = 0; Int32 leftDigitInt; Int32 rightDigitInt; Int64 i = 0; // left-most iterator in expanded sequence Int32 leftFileID = 0; // 1 will be subtracted on first iteration, to make this (fileData.Length-1)/2 Int32 rightFileID = (fileData.Length+1)/2; // j = right-most position in compressed sequence; 1 will be subtracted on first iteration, to make this fileData.Length-1 Int32 j = fileData.Length; Int32 leftBlockLength; Int32 rightBlockLength = 0; Boolean leftIsFileBlock = true; Boolean rightIsFileBlock = (fileData.Length % 2) == 1; foreach(Char digitChar in fileData) { // The .toString() is needed to convert '2' to "2" to get 2 from .ToInt32(), otherwise we get the // ASCII value of '2' (when we just wanted the number 2) when converting a Char ('2') to Int32. leftDigitInt = System.Convert.ToInt32(digitChar.ToString()); if (leftIsFileBlock) { leftBlockLength = leftDigitInt; // part of the same file block was handled from the right end, // so only the remaining length of that block is to be handled by // from the left end if (leftFileID == rightFileID) { leftBlockLength = rightBlockLength; } // fileID * Sum(A_n from i to i+n-1) checksum += leftFileID * (leftBlockLength * (2*i + leftBlockLength - 1) / 2); //for (int l = 0; l < leftBlockLength; l++) // Console.Write("{0:d},", leftFileID); i += leftBlockLength; leftFileID += 1; // immediately after handling the leftFileID == rightFileID case, we should exit the // loop after having incremented leftFileID above, because it has already been fully // handled from the right end - and thus has already been incorporated in the checksum. if (leftFileID > rightFileID) { // to remove trailing comma //Console.Write("\b "); break; } } else { // left is free block leftBlockLength = leftDigitInt; if (rightBlockLength < 0) throw new ApplicationException(nameof(rightBlockLength)+" must be >= 0 but was "+rightBlockLength.ToString()); if (rightBlockLength == 0) { j -= 1; rightIsFileBlock = !rightIsFileBlock; rightDigitInt = System.Convert.ToInt32(fileData[j].ToString()); rightFileID -= 1; rightBlockLength = rightDigitInt; } //Console.Write("\b["); while (leftBlockLength >= rightBlockLength) { checksum += rightFileID * (rightBlockLength * (2 * i + rightBlockLength - 1) / 2); i += rightBlockLength; //for (int l = 0; l < rightBlockLength; l++) // Console.Write("{0:d},", rightFileID); leftBlockLength -= rightBlockLength; // right-ended free block j -= 1; rightIsFileBlock = !rightIsFileBlock; rightDigitInt = System.Convert.ToInt32(fileData[j].ToString()); // moving leftward from the right-ended free block, so handling a file block j -= 1; rightIsFileBlock = !rightIsFileBlock; rightDigitInt = System.Convert.ToInt32(fileData[j].ToString()); rightFileID -= 1; // if there are more empty spaces left (leftBlockLength) in the free block handled // from the left side but no more right handled file blocks to move there (if // rightFileID > leftFileID), we've reached the end of the checksum calculation and // should exit the loop without adding anything further to the checksum if (leftFileID > rightFileID) break; rightBlockLength = rightDigitInt; } // if we broke from the inner 'for' loop above because leftFileID > rightFileID, we should break // out of the outer 'foreach' loop too, for the same reason if (leftFileID > rightFileID) { //Console.Write("\b]"); break; } if (leftBlockLength > 0) { // Also, leftBlockLength < rightBlockLength after the above while() loop checksum += rightFileID * (leftBlockLength * (2 * i + leftBlockLength - 1) / 2); i += leftBlockLength; //for (int l = 0; l < leftBlockLength; l++) // Console.Write("{0:d},", rightFileID); rightBlockLength -= leftBlockLength; } else //Console.Write(","); // to prevent opening [ from being eaten by the \b on the next line //Console.Write("\b]"); ; } leftIsFileBlock = !leftIsFileBlock; } //Console.WriteLine(); Console.WriteLine("checksum = {0:d}", checksum); // part 2 { // Linked list of tuples of (fileID, fileLength) tuples, with fileID = -1 for free lengths LinkedList<(Int32, Int32)> expandedDisk = new(); Int32 digitInt; Int32 previousFileID = -2; Int32 fileID = 0; Int32 fileLength = 0; Boolean isFileBlock = true; // create expanded disk from disk map foreach(Char digitChar in fileData) { digitInt = System.Convert.ToInt32(digitChar.ToString()); if (isFileBlock) { expandedDisk.AddLast((fileID, digitInt)); for (Int32 ii = 0; ii < digitInt; ii++) Console.Write("{0:d},", fileID); fileID++; } else { for (Int32 ii = 0; ii < digitInt; ii++) Console.Write('.'); expandedDisk.AddLast((-1, digitInt)); } isFileBlock = !isFileBlock; } Console.WriteLine(); for (LinkedListNode<(Int32, Int32)>? node = expandedDisk.Last; node != null; node = node.Previous) { fileLength = node.Value.Item2; fileID = node.Value.Item1; if (fileID == -1) // node is not free space continue; // previousFileId is no longer the negative sentinel value // and we've finished progressing down the fileIDs in descending order // and have hit up against a previously moved fileID if (previousFileID >= 0 && previousFileID < fileID) { //Console.WriteLine("previousFileID = {0:d}, fileID = {1:d}", previousFileID, fileID); previousFileID = fileID; continue; } for (LinkedListNode<(Int32, Int32)>? innerNode = expandedDisk.First; innerNode != node && innerNode != null; innerNode = innerNode.Next) { Int32 freeID = innerNode.Value.Item1; Int32 freeLength = innerNode.Value.Item2; //for (Int32 ii = 0; ii < freeLength; ii++) // if (freeID == -1) // Console.Write('.'); // else // Console.Write("{0:d},", freeID); if (freeID != -1) // node is not free space continue; if (fileLength <= freeLength) { // moving file to formerly free space expandedDisk.AddBefore(innerNode, (fileID, fileLength)); // changing original node to free space node.ValueRef = (-1, fileLength); // reducing or deleting free space if (freeLength - fileLength == 0) expandedDisk.Remove(innerNode); else innerNode.ValueRef = (-1, freeLength - fileLength); // fileID of free space is -1 break; } } //Console.WriteLine(); previousFileID = fileID; } //Console.WriteLine(); Int32 blockPosition = 0; foreach(var fileBlockTuple in expandedDisk) { fileID = fileBlockTuple.Item1; fileLength = fileBlockTuple.Item2; // skip free blocks if (fileID == -1) { for (Int32 ii = 0; ii < fileLength; ii++) Console.Write('.'); blockPosition += fileLength; continue; } checksumWholeFile += fileID * (fileLength * (2 * blockPosition + fileLength - 1) / 2); //Console.WriteLine("\n(intermediate) fileID = {0:d}, blockPosition = {1:d}, fileLength = {2:d}", // fileID, blockPosition, fileLength); //Console.WriteLine("(intermediate) checksumWholeFile = {0:d}", checksumWholeFile); for (Int32 ii = 0; ii < fileLength; ii++) Console.Write("{0:d},", fileID); blockPosition += fileLength; } } Console.WriteLine(); Console.WriteLine("checksumWholeFile = {0:d}", checksumWholeFile); // to keep the console window from closing Console.ReadKey(); } } }