using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Spectre.Console; namespace AdventOfCode.Days; public class Day2 : Day { public override int Number => 2; public override string Name => "Gift Shop"; public override void RunPart1(bool display = true) { var inputSpan = Input.AsSpan(); var sum = 0L; // Parse the ranges var idsCount = inputSpan.Count(',') + 1; Span idRanges = stackalloc IdRange[idsCount]; var index = 0; var maxDigits = 0; foreach (var spanRange in inputSpan.Split(',')) { var span = inputSpan[spanRange]; var separatorIndex = span.IndexOf('-'); var lowerBound = long.Parse(span[..separatorIndex]); var upperBound = long.Parse(span[(separatorIndex + 1)..]); idRanges[index] = new IdRange(lowerBound, upperBound); index++; // Also find the biggest number var digitsCount = CountDigits((ulong) upperBound); if (digitsCount > maxDigits) { maxDigits = digitsCount; } } // For each possible invalid ID, check if it's in a range var maxSingleId = 1L * (long) Math.Pow(10, Math.Ceiling(maxDigits / 2D)) - 1L; for (var invalidIdSingle = 1L; invalidIdSingle <= maxSingleId; invalidIdSingle++) { // Build invalid ID var invalidId = (invalidIdSingle * (long) Math.Pow(10, (int) Math.Log10(invalidIdSingle) + 1)) + invalidIdSingle; // Check if invalid ID is in a range // This could be optimized by building a sorted list of ranges and using binary search foreach (var idRange in idRanges) { if (invalidId >= idRange.Lower && invalidId <= idRange.Upper) { sum += invalidId; break; } } } if (display) { AnsiConsole.MarkupLine($"[green]Sum of invalid IDs: [yellow]{sum}[/][/]"); } } public override void RunPart2(bool display = true) { var inputSpan = Input.AsSpan(); var sum = 0L; // Parse the ranges var idsCount = inputSpan.Count(',') + 1; Span idRanges = stackalloc IdRange[idsCount]; var index = 0; var maxDigits = 0; foreach (var spanRange in inputSpan.Split(',')) { var span = inputSpan[spanRange]; var separatorIndex = span.IndexOf('-'); var lowerBound = long.Parse(span[..separatorIndex]); var upperBound = long.Parse(span[(separatorIndex + 1)..]); idRanges[index] = new IdRange(lowerBound, upperBound); index++; // Also find the biggest number var digitsCount = CountDigits((ulong) upperBound); if (digitsCount > maxDigits) { maxDigits = digitsCount; } } // For each possible invalid ID, check if it's in a range Span invalidIds = stackalloc long[maxDigits]; var visitedIds = new HashSet(); var maxSingleId = 1L * (long) Math.Pow(10, Math.Ceiling(maxDigits / 2D)) - 1L; for (var invalidIdSingle = 1L; invalidIdSingle <= maxSingleId; invalidIdSingle++) { // Build invalid IDs var filledCount = FillPossibleInvalidIds(invalidIds, invalidIdSingle, maxDigits); // Check if invalid ID is in a range // This could be optimized by building a sorted list of ranges and using binary search for (var i = 0; i < filledCount; i++) { var invalidId = invalidIds[i]; if (visitedIds.Add(invalidId)) { foreach (var idRange in idRanges) { if (invalidId >= idRange.Lower && invalidId <= idRange.Upper) { sum += invalidId; break; } } } } } if (display) { AnsiConsole.MarkupLine($"[green]Sum of invalid IDs: [yellow]{sum}[/][/]"); } return; static int FillPossibleInvalidIds(Span invalidIds, long invalidIdSingle, int maxDigits) { Span idSpanBuffer = stackalloc char[20]; Span target = stackalloc char[maxDigits]; // Write number to char span invalidIdSingle.TryFormat(idSpanBuffer, out var idLength); var idSpan = idSpanBuffer[..idLength]; // First part will always be the single id idSpan.CopyTo(target); var times = 2; var index = 0; while (true) { var targetSize = idSpan.Length * times; // Check if result number would be too big if (targetSize > target.Length) { break; } // Build concatenated string for (var i = 1; i < times; i++) { idSpan.CopyTo(target[(idSpan.Length * i)..]); } invalidIds[index] = long.Parse(target[..targetSize]); times++; index++; } return index; } } private record struct IdRange(long Lower, long Upper); // Source: https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs,b2bb39bb9360e1d1,references [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int CountDigits(ulong value) { // Map the log2(value) to a power of 10. ReadOnlySpan log2ToPow10 = [ 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20 ]; nint elementOffset = log2ToPow10[(int)ulong.Log2(value)]; // Read the associated power of 10. ReadOnlySpan powersOf10 = [ 0, // unused entry to avoid needing to subtract 0, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000, 100000000000000, 1000000000000000, 10000000000000000, 100000000000000000, 1000000000000000000, 10000000000000000000, ]; ulong powerOf10 = Unsafe.Add(ref MemoryMarshal.GetReference(powersOf10), elementOffset); // Return the number of digits based on the power of 10, shifted by 1 // if it falls below the threshold. int index = (int)elementOffset; return index - (value < powerOf10 ? 1 : 0); } }