Files
AdventOfCode/Days/Day2.cs
2025-12-03 20:20:28 +01:00

229 lines
7.2 KiB
C#

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<IdRange> 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<IdRange> 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<long> invalidIds = stackalloc long[maxDigits];
var visitedIds = new HashSet<long>();
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<long> invalidIds, long invalidIdSingle, int maxDigits)
{
Span<char> idSpanBuffer = stackalloc char[20];
Span<char> 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<byte> 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<ulong> 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);
}
}