diff --git a/src/AoCHelper/AoCHelper.csproj b/src/AoCHelper/AoCHelper.csproj
index 8f22152..77f54f9 100644
--- a/src/AoCHelper/AoCHelper.csproj
+++ b/src/AoCHelper/AoCHelper.csproj
@@ -26,6 +26,7 @@
+
diff --git a/src/AoCHelper/BaseProblem.cs b/src/AoCHelper/BaseProblem.cs
index b099bcc..bd68c33 100644
--- a/src/AoCHelper/BaseProblem.cs
+++ b/src/AoCHelper/BaseProblem.cs
@@ -28,7 +28,7 @@ public virtual uint CalculateIndex()
{
var typeName = GetType().Name;
- return uint.TryParse(typeName.Substring(typeName.IndexOf(ClassPrefix) + ClassPrefix.Length).TrimStart('_'), out var index)
+ return uint.TryParse(typeName[(typeName.IndexOf(ClassPrefix) + ClassPrefix.Length)..].TrimStart('_'), out var index)
? index
: default;
}
diff --git a/src/AoCHelper/IsExternalInit.cs b/src/AoCHelper/IsExternalInit.cs
index 25732f0..47e2340 100644
--- a/src/AoCHelper/IsExternalInit.cs
+++ b/src/AoCHelper/IsExternalInit.cs
@@ -16,9 +16,7 @@ namespace System.Runtime.CompilerServices
/// This class should not be used by developers in source code.
///
[EditorBrowsable(EditorBrowsableState.Never)]
- internal static class IsExternalInit
- {
- }
+ internal static class IsExternalInit;
}
#endif
\ No newline at end of file
diff --git a/src/AoCHelper/Range.cs b/src/AoCHelper/Range.cs
new file mode 100644
index 0000000..3db71d4
--- /dev/null
+++ b/src/AoCHelper/Range.cs
@@ -0,0 +1,280 @@
+// Based on https://www.meziantou.net/how-to-use-csharp-8-indices-and-ranges-in-dotnet-standard-2-0-and-dotn.htm
+
+// https://github.com/dotnet/runtime/blob/419e949d258ecee4c40a460fb09c66d974229623/src/libraries/System.Private.CoreLib/src/System/Index.cs
+// https://github.com/dotnet/runtime/blob/419e949d258ecee4c40a460fb09c66d974229623/src/libraries/System.Private.CoreLib/src/System/Range.cs
+
+using System.Runtime.CompilerServices;
+
+namespace System
+{
+ /// Represent a type can be used to index a collection either from the start or the end.
+ ///
+ /// Index is used by the C# compiler to support the new index syntax
+ ///
+ /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ;
+ /// int lastElement = someArray[^1]; // lastElement = 5
+ ///
+ ///
+ internal readonly struct Index : IEquatable
+ {
+ private readonly int _value;
+
+ /// Construct an Index using a value and indicating if the index is from the start or from the end.
+ /// The index value. it has to be zero or positive number.
+ /// Indicating if the index is from the start or from the end.
+ ///
+ /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#pragma warning disable S3427 // Method overloads with default parameter values should not overlap
+ public Index(int value, bool fromEnd = false)
+#pragma warning restore S3427 // Method overloads with default parameter values should not overlap
+ {
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative");
+ }
+
+ if (fromEnd)
+ _value = ~value;
+ else
+ _value = value;
+ }
+
+ // The following private constructors mainly created for perf reason to avoid the checks
+ private Index(int value)
+ {
+ _value = value;
+ }
+
+ /// Create an Index pointing at first element.
+ public static Index Start => new(0);
+
+ /// Create an Index pointing at beyond last element.
+ public static Index End => new(~0);
+
+ /// Create an Index from the start at the position indicated by the value.
+ /// The index value from the start.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Index FromStart(int value)
+ {
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative");
+ }
+
+ return new Index(value);
+ }
+
+ /// Create an Index from the end at the position indicated by the value.
+ /// The index value from the end.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Index FromEnd(int value)
+ {
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative");
+ }
+
+ return new Index(~value);
+ }
+
+ /// Returns the index value.
+ public int Value
+ {
+ get
+ {
+ if (_value < 0)
+ {
+ return ~_value;
+ }
+ else
+ {
+ return _value;
+ }
+ }
+ }
+
+ /// Indicates whether the index is from the start or the end.
+ public bool IsFromEnd => _value < 0;
+
+ /// Calculate the offset from the start using the giving collection length.
+ /// The length of the collection that the Index will be used with. length has to be a positive value
+ ///
+ /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values.
+ /// we don't validate either the returned offset is greater than the input length.
+ /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and
+ /// then used to index a collection will get out of range exception which will be same affect as the validation.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int GetOffset(int length)
+ {
+ var offset = _value;
+ if (IsFromEnd)
+ {
+ // offset = length - (~value)
+ // offset = length + (~(~value) + 1)
+ // offset = length + value + 1
+
+ offset += length + 1;
+ }
+ return offset;
+ }
+
+ /// Indicates whether the current Index object is equal to another object of the same type.
+ /// An object to compare with this object
+ public override bool Equals(object? obj) => obj is Index index && _value == (index)._value;
+
+ /// Indicates whether the current Index object is equal to another Index object.
+ /// An object to compare with this object
+ public bool Equals(Index other) => _value == other._value;
+
+ /// Returns the hash code for this instance.
+ public override int GetHashCode() => _value;
+
+ /// Converts integer number to an Index.
+ public static implicit operator Index(int value) => FromStart(value);
+
+ /// Converts the value of the current Index object to its equivalent string representation.
+ public override string ToString()
+ {
+ if (IsFromEnd)
+ return "^" + ((uint)Value).ToString();
+
+ return ((uint)Value).ToString();
+ }
+ }
+
+ /// Represent a range has start and end indexes.
+ ///
+ /// Range is used by the C# compiler to support the range syntax.
+ ///
+ /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 };
+ /// int[] subArray1 = someArray[0..2]; // { 1, 2 }
+ /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 }
+ ///
+ ///
+ internal readonly struct Range : IEquatable
+ {
+ /// Represent the inclusive start index of the Range.
+ public Index Start { get; }
+
+ /// Represent the exclusive end index of the Range.
+ public Index End { get; }
+
+ /// Construct a Range object using the start and end indexes.
+ /// Represent the inclusive start index of the range.
+ /// Represent the exclusive end index of the range.
+ public Range(Index start, Index end)
+ {
+ Start = start;
+ End = end;
+ }
+
+ /// Indicates whether the current Range object is equal to another object of the same type.
+ /// An object to compare with this object
+ public override bool Equals(object? obj) =>
+ obj is Range r &&
+ r.Start.Equals(Start) &&
+ r.End.Equals(End);
+
+ /// Indicates whether the current Range object is equal to another Range object.
+ /// An object to compare with this object
+ public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End);
+
+ /// Returns the hash code for this instance.
+ public override int GetHashCode()
+ {
+ return (Start.GetHashCode() * 31) + End.GetHashCode();
+ }
+
+ /// Converts the value of the current Range object to its equivalent string representation.
+ public override string ToString()
+ {
+ return Start + ".." + End;
+ }
+
+ /// Create a Range object starting from start index to the end of the collection.
+ public static Range StartAt(Index start) => new Range(start, Index.End);
+
+ /// Create a Range object starting from first element in the collection to the end Index.
+ public static Range EndAt(Index end) => new Range(Index.Start, end);
+
+ /// Create a Range object starting from first element to the end.
+ public static Range All => new Range(Index.Start, Index.End);
+
+ /// Calculate the start offset and length of range object using a collection length.
+ /// The length of the collection that the range will be used with. length has to be a positive value.
+ ///
+ /// For performance reason, we don't validate the input length parameter against negative values.
+ /// It is expected Range will be used with collections which always have non negative length/count.
+ /// We validate the range is inside the length scope though.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public (int Offset, int Length) GetOffsetAndLength(int length)
+ {
+ int start;
+ var startIndex = Start;
+ if (startIndex.IsFromEnd)
+ start = length - startIndex.Value;
+ else
+ start = startIndex.Value;
+
+ int end;
+ var endIndex = End;
+ if (endIndex.IsFromEnd)
+ end = length - endIndex.Value;
+ else
+ end = endIndex.Value;
+
+ if ((uint)end > (uint)length || (uint)start > (uint)end)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length));
+ }
+
+ return (start, end - start);
+ }
+ }
+}
+
+namespace System.Runtime.CompilerServices
+{
+ internal static class RuntimeHelpers
+ {
+ ///
+ /// Slices the specified array using the specified range.
+ ///
+ public static T[] GetSubArray(T[] array, Range range)
+ {
+ if (array == null)
+ {
+ throw new ArgumentNullException(nameof(array));
+ }
+
+ (int offset, int length) = range.GetOffsetAndLength(array.Length);
+
+#pragma warning disable S2955 // Generic parameters not constrained to reference types should not be compared to "null"
+ if (default(T) != null || typeof(T[]) == array.GetType())
+ {
+ // We know the type of the array to be exactly T[].
+
+ if (length == 0)
+ {
+ return Array.Empty();
+ }
+
+ var dest = new T[length];
+ Array.Copy(array, offset, dest, 0, length);
+ return dest;
+ }
+ else
+ {
+ // The array is actually a U[] where U:T.
+ var dest = (T[])Array.CreateInstance(array.GetType().GetElementType(), length);
+ Array.Copy(array, offset, dest, 0, length);
+ return dest;
+ }
+#pragma warning restore S2955 // Generic parameters not constrained to reference types should not be compared to "null"
+ }
+ }
+}
diff --git a/src/AoCHelper/Solver.cs b/src/AoCHelper/Solver.cs
index f5b70a6..c1333b7 100644
--- a/src/AoCHelper/Solver.cs
+++ b/src/AoCHelper/Solver.cs
@@ -1,6 +1,7 @@
using System.Diagnostics;
using System.Reflection;
using Spectre.Console;
+using System.Linq;
namespace AoCHelper;
@@ -8,7 +9,7 @@ public static class Solver
{
private static readonly bool IsInteractiveEnvironment = Environment.UserInteractive && !Console.IsOutputRedirected;
- private record ElapsedTime(double Constructor, double Part1, double Part2);
+ private sealed record ElapsedTime(double Constructor, double Part1, double Part2);
///
/// Solves last problem.
@@ -137,23 +138,19 @@ await AnsiConsole.Live(table)
.StartAsync(async ctx =>
{
var sw = new Stopwatch();
- foreach (Type problemType in LoadAllProblems(configuration.ProblemAssemblies))
+ foreach (var problemType in LoadAllProblems(configuration.ProblemAssemblies).Where(problemType => problems.Contains(problemType)))
{
- if (problems.Contains(problemType))
+ sw.Restart();
+ var potentialProblem = InstantiateProblem(problemType);
+ sw.Stop();
+ if (potentialProblem is BaseProblem problem)
{
- sw.Restart();
- var potentialProblem = InstantiateProblem(problemType);
- sw.Stop();
-
- if (potentialProblem is BaseProblem problem)
- {
- totalElapsedTime.Add(await SolveProblem(problem, table, CalculateElapsedMilliseconds(sw), configuration));
- ctx.Refresh();
- }
- else
- {
- totalElapsedTime.Add(RenderEmptyProblem(problemType, potentialProblem as string, table, CalculateElapsedMilliseconds(sw), configuration));
- }
+ totalElapsedTime.Add(await SolveProblem(problem, table, CalculateElapsedMilliseconds(sw), configuration));
+ ctx.Refresh();
+ }
+ else
+ {
+ totalElapsedTime.Add(RenderEmptyProblem(problemType, potentialProblem as string, table, CalculateElapsedMilliseconds(sw), configuration));
}
}
});
@@ -442,9 +439,9 @@ private static void RenderOverallResultsPanel(List totalElapsedTime
return;
}
- var totalConstructors = totalElapsedTime.Select(t => t.Constructor).Sum();
- var totalPart1 = totalElapsedTime.Select(t => t.Part1).Sum();
- var totalPart2 = totalElapsedTime.Select(t => t.Part2).Sum();
+ var totalConstructors = totalElapsedTime.Sum(t => t.Constructor);
+ var totalPart1 = totalElapsedTime.Sum(t => t.Part1);
+ var totalPart2 = totalElapsedTime.Sum(t => t.Part2);
var total = totalPart1 + totalPart2 + (configuration.ShowConstructorElapsedTime ? totalConstructors : 0);
var grid = new Grid()
@@ -466,12 +463,12 @@ private static void RenderOverallResultsPanel(List totalElapsedTime
if (configuration.ShowConstructorElapsedTime)
{
- grid.AddRow("Mean constructors", FormatTime(totalElapsedTime.Select(t => t.Constructor).Average(), configuration));
+ grid.AddRow("Mean constructors", FormatTime(totalElapsedTime.Average(t => t.Constructor), configuration));
}
grid
- .AddRow("Mean parts 1", FormatTime(totalElapsedTime.Select(t => t.Part1).Average(), configuration))
- .AddRow("Mean parts 2", FormatTime(totalElapsedTime.Select(t => t.Part2).Average(), configuration));
+ .AddRow("Mean parts 1", FormatTime(totalElapsedTime.Average(t => t.Part1), configuration))
+ .AddRow("Mean parts 2", FormatTime(totalElapsedTime.Average(t => t.Part2), configuration));
AnsiConsole.Write(
new Panel(grid)
diff --git a/tests/AoCHelper.Test/SolverTest.cs b/tests/AoCHelper.Test/SolverTest.cs
index 6bbde53..96c6c17 100644
--- a/tests/AoCHelper.Test/SolverTest.cs
+++ b/tests/AoCHelper.Test/SolverTest.cs
@@ -67,8 +67,8 @@ public async Task SolveIntParams()
[Fact]
public async Task SolveIntEnumerable()
{
- await Solver.Solve(new List { 1, 2 });
- await Solver.Solve(new List { 1, 2 }, _ => { });
+ await Solver.Solve([1, 2]);
+ await Solver.Solve([1, 2], _ => { });
}
[Fact]
@@ -81,8 +81,8 @@ public async Task SolveTypeParams()
[Fact]
public async Task SolveTypeEnumerable()
{
- await Solver.Solve(new List { typeof(Problem66) });
- await Solver.Solve(new List { typeof(Problem66) }, _ => { });
+ await Solver.Solve([typeof(Problem66)]);
+ await Solver.Solve([typeof(Problem66)], _ => { });
}
[Fact]