PlotDirector/PlotLine/Services/NaturalStringComparer.cs

81 lines
2.0 KiB
C#

namespace PlotLine.Services;
public sealed class NaturalStringComparer : IComparer<string?>
{
public static readonly NaturalStringComparer OrdinalIgnoreCase = new();
private NaturalStringComparer()
{
}
public int Compare(string? x, string? y)
{
if (ReferenceEquals(x, y))
{
return 0;
}
if (x is null)
{
return -1;
}
if (y is null)
{
return 1;
}
var xIndex = 0;
var yIndex = 0;
while (xIndex < x.Length && yIndex < y.Length)
{
if (char.IsDigit(x[xIndex]) && char.IsDigit(y[yIndex]))
{
var numberCompare = CompareNumberRuns(x, ref xIndex, y, ref yIndex);
if (numberCompare != 0)
{
return numberCompare;
}
continue;
}
var charCompare = char.ToUpperInvariant(x[xIndex]).CompareTo(char.ToUpperInvariant(y[yIndex]));
if (charCompare != 0)
{
return charCompare;
}
xIndex++;
yIndex++;
}
return x.Length.CompareTo(y.Length);
}
private static int CompareNumberRuns(string x, ref int xIndex, string y, ref int yIndex)
{
var xStart = xIndex;
var yStart = yIndex;
while (xIndex < x.Length && char.IsDigit(x[xIndex]))
{
xIndex++;
}
while (yIndex < y.Length && char.IsDigit(y[yIndex]))
{
yIndex++;
}
var xRun = x[xStart..xIndex].TrimStart('0');
var yRun = y[yStart..yIndex].TrimStart('0');
if (xRun.Length != yRun.Length)
{
return xRun.Length.CompareTo(yRun.Length);
}
var valueCompare = string.Compare(xRun, yRun, StringComparison.Ordinal);
return valueCompare != 0 ? valueCompare : (xIndex - xStart).CompareTo(yIndex - yStart);
}
}