using System; using System.Collections.Generic; namespace Jellyfin.Extensions { /// /// Alphanumeric . /// public class AlphanumericComparator : IComparer { /// /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. /// /// The first object to compare. /// The second object to compare. /// A signed integer that indicates the relative values of x and y. public static int CompareValues(string? s1, string? s2) { if (s1 == null && s2 == null) { return 0; } else if (s1 == null) { return -1; } else if (s2 == null) { return 1; } int len1 = s1.Length; int len2 = s2.Length; // Early return for empty strings if (len1 == 0 && len2 == 0) { return 0; } else if (len1 == 0) { return -1; } else if (len2 == 0) { return 1; } int pos1 = 0; int pos2 = 0; do { int start1 = pos1; int start2 = pos2; bool isNum1 = char.IsDigit(s1[pos1++]); bool isNum2 = char.IsDigit(s2[pos2++]); while (pos1 < len1 && char.IsDigit(s1[pos1]) == isNum1) { pos1++; } while (pos2 < len2 && char.IsDigit(s2[pos2]) == isNum2) { pos2++; } var span1 = s1.AsSpan(start1, pos1 - start1); var span2 = s2.AsSpan(start2, pos2 - start2); if (isNum1 && isNum2) { // Trim leading zeros so we can compare the length // of the strings to find the largest number span1 = span1.TrimStart('0'); span2 = span2.TrimStart('0'); var span1Len = span1.Length; var span2Len = span2.Length; if (span1Len < span2Len) { return -1; } else if (span1Len > span2Len) { return 1; } else if (span1Len >= 20) // Number is probably too big for a ulong { // Trim all the first digits that are the same int i = 0; while (i < span1Len && span1[i] == span2[i]) { i++; } // If there are no more digits it's the same number if (i == span1Len) { continue; } // Only need to compare the most significant digit span1 = span1.Slice(i, 1); span2 = span2.Slice(i, 1); } if (!ulong.TryParse(span1, out var num1) || !ulong.TryParse(span2, out var num2)) { return 0; } else if (num1 < num2) { return -1; } else if (num1 > num2) { return 1; } } else { int result = span1.CompareTo(span2, StringComparison.InvariantCulture); if (result != 0) { return result; } } #pragma warning disable SA1500 // TODO remove with StyleCop.Analyzers v1.2.0 https://github.com/DotNetAnalyzers/StyleCopAnalyzers/pull/3196 } while (pos1 < len1 && pos2 < len2); #pragma warning restore SA1500 return len1 - len2; } /// public int Compare(string? x, string? y) { return CompareValues(x, y); } } }