namespace PlotLine.Services; public sealed class NaturalStringComparer : IComparer { 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); } }