AlphanumComparator.cs 3.9 KB

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