AlphanumComparator.cs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. #pragma warning disable CS1591
  2. #nullable enable
  3. using System;
  4. using System.Collections.Generic;
  5. namespace MediaBrowser.Controller.Sorting
  6. {
  7. public class AlphanumComparator : IComparer<string?>
  8. {
  9. public static int CompareValues(string? s1, string? s2)
  10. {
  11. if (s1 == null && s2 == null)
  12. {
  13. return 0;
  14. }
  15. else if (s1 == null)
  16. {
  17. return -1;
  18. }
  19. else if (s2 == null)
  20. {
  21. return 1;
  22. }
  23. int len1 = s1.Length;
  24. int len2 = s2.Length;
  25. // Early return for empty strings
  26. if (len1 == 0 && len2 == 0)
  27. {
  28. return 0;
  29. }
  30. else if (len1 == 0)
  31. {
  32. return -1;
  33. }
  34. else if (len2 == 0)
  35. {
  36. return 1;
  37. }
  38. int pos1 = 0;
  39. int pos2 = 0;
  40. do
  41. {
  42. int start1 = pos1;
  43. int start2 = pos2;
  44. bool isNum1 = char.IsDigit(s1[pos1++]);
  45. bool isNum2 = char.IsDigit(s2[pos2++]);
  46. while (pos1 < len1 && char.IsDigit(s1[pos1]) == isNum1)
  47. {
  48. pos1++;
  49. }
  50. while (pos2 < len2 && char.IsDigit(s2[pos2]) == isNum2)
  51. {
  52. pos2++;
  53. }
  54. var span1 = s1.AsSpan(start1, pos1 - start1);
  55. var span2 = s2.AsSpan(start2, pos2 - start2);
  56. if (isNum1 && isNum2)
  57. {
  58. // Trim leading zeros so we can compare the length
  59. // of the strings to find the largest number
  60. span1 = span1.TrimStart('0');
  61. span2 = span2.TrimStart('0');
  62. var span1Len = span1.Length;
  63. var span2Len = span2.Length;
  64. if (span1Len < span2Len)
  65. {
  66. return -1;
  67. }
  68. else if (span1Len > span2Len)
  69. {
  70. return 1;
  71. }
  72. else if (span1Len >= 20) // Number is probably too big for a ulong
  73. {
  74. // Trim all the first digits that are the same
  75. int i = 0;
  76. while (i < span1Len && span1[i] == span2[i])
  77. {
  78. i++;
  79. }
  80. // If there are no more digits it's the same number
  81. if (i == span1Len)
  82. {
  83. continue;
  84. }
  85. // Only need to compare the most significant digit
  86. span1 = span1.Slice(i, 1);
  87. span2 = span2.Slice(i, 1);
  88. }
  89. if (!ulong.TryParse(span1, out var num1)
  90. || !ulong.TryParse(span2, out var num2))
  91. {
  92. return 0;
  93. }
  94. else if (num1 < num2)
  95. {
  96. return -1;
  97. }
  98. else if (num1 > num2)
  99. {
  100. return 1;
  101. }
  102. }
  103. else
  104. {
  105. int result = span1.CompareTo(span2, StringComparison.InvariantCulture);
  106. if (result != 0)
  107. {
  108. return result;
  109. }
  110. }
  111. } while (pos1 < len1 && pos2 < len2);
  112. return len1 - len2;
  113. }
  114. /// <inheritdoc />
  115. public int Compare(string? x, string? y)
  116. {
  117. return CompareValues(x, y);
  118. }
  119. }
  120. }