@@ -7,32 +7,110 @@ public sealed class NaturalStringComparer
77 {
88 public static IComparer < object > GetForProcessor ( )
99 {
10- return Win32Helper . IsRunningOnArm ? new StringComparerArm64 ( ) : new StringComparerDefault ( ) ;
10+ return new NaturalComparer ( StringComparison . CurrentCulture ) ;
1111 }
12-
13- private sealed class StringComparerArm64 : IComparer < object >
12+
13+ private sealed class NaturalComparer : IComparer < object ? > , IComparer < string ? > , IComparer < ReadOnlyMemory < char > >
1414 {
15- public int Compare ( object a , object b )
16- {
17- return StringComparer . CurrentCulture . Compare ( a , b ) ;
18- }
19- }
15+ private readonly StringComparison stringComparison ;
2016
21- private sealed class StringComparerDefault : IComparer < object >
22- {
23- public int Compare ( object a , object b )
24- {
25- return Win32PInvoke . CompareStringEx (
26- Win32PInvoke . LOCALE_NAME_USER_DEFAULT ,
27- Win32PInvoke . SORT_DIGITSASNUMBERS , // Add other flags if required.
28- a ? . ToString ( ) ,
29- a ? . ToString ( ) . Length ?? 0 ,
30- b ? . ToString ( ) ,
31- b ? . ToString ( ) . Length ?? 0 ,
32- IntPtr . Zero ,
33- IntPtr . Zero ,
34- 0 ) - 2 ;
35- }
17+ public NaturalComparer ( StringComparison stringComparison = StringComparison . Ordinal )
18+ {
19+ this . stringComparison = stringComparison ;
20+ }
21+
22+ public int Compare ( object ? x , object ? y )
23+ {
24+ if ( x == y ) return 0 ;
25+ if ( x == null ) return - 1 ;
26+ if ( y == null ) return 1 ;
27+
28+ return x switch
29+ {
30+ string x1 when y is string y1 => Compare ( x1 . AsSpan ( ) , y1 . AsSpan ( ) , stringComparison ) ,
31+ IComparable comparable => comparable . CompareTo ( y ) ,
32+ _ => StringComparer . FromComparison ( stringComparison ) . Compare ( x , y )
33+ } ;
34+ }
35+
36+ public int Compare ( string ? x , string ? y )
37+ {
38+ if ( ReferenceEquals ( x , y ) ) return 0 ;
39+ if ( x is null ) return - 1 ;
40+ if ( y is null ) return 1 ;
41+
42+ return Compare ( x . AsSpan ( ) , y . AsSpan ( ) , stringComparison ) ;
43+ }
44+
45+ public int Compare ( ReadOnlySpan < char > x , ReadOnlySpan < char > y )
46+ {
47+ return Compare ( x , y , stringComparison ) ;
48+ }
49+
50+ public int Compare ( ReadOnlyMemory < char > x , ReadOnlyMemory < char > y )
51+ {
52+ return Compare ( x . Span , y . Span , stringComparison ) ;
53+ }
54+
55+ public static int Compare ( ReadOnlySpan < char > x , ReadOnlySpan < char > y , StringComparison stringComparison )
56+ {
57+ var length = Math . Min ( x . Length , y . Length ) ;
58+
59+ for ( var i = 0 ; i < length ; i ++ )
60+ {
61+ if ( char . IsDigit ( x [ i ] ) && char . IsDigit ( y [ i ] ) )
62+ {
63+ var xOut = GetNumber ( x . Slice ( i ) , out var xNumAsSpan ) ;
64+ var yOut = GetNumber ( y . Slice ( i ) , out var yNumAsSpan ) ;
65+
66+ var compareResult = CompareNumValues ( xNumAsSpan , yNumAsSpan ) ;
67+
68+ if ( compareResult != 0 ) return compareResult ;
69+
70+ i = - 1 ;
71+ length = Math . Min ( xOut . Length , yOut . Length ) ;
72+
73+ x = xOut ;
74+ y = yOut ;
75+ continue ;
76+ }
77+
78+ var charCompareResult = x . Slice ( i , 1 ) . CompareTo ( y . Slice ( i , 1 ) , stringComparison ) ;
79+ if ( charCompareResult != 0 ) return charCompareResult ;
80+ }
81+
82+ return x . Length . CompareTo ( y . Length ) ;
83+ }
84+
85+ private static ReadOnlySpan < char > GetNumber ( ReadOnlySpan < char > span , out ReadOnlySpan < char > number )
86+ {
87+ var i = 0 ;
88+ while ( i < span . Length && char . IsDigit ( span [ i ] ) )
89+ {
90+ i ++ ;
91+ }
92+
93+ number = span . Slice ( 0 , i ) ;
94+ return span . Slice ( i ) ;
95+ }
96+
97+ private static int CompareNumValues ( ReadOnlySpan < char > numValue1 , ReadOnlySpan < char > numValue2 )
98+ {
99+ var num1AsSpan = numValue1 . TrimStart ( '0' ) ;
100+ var num2AsSpan = numValue2 . TrimStart ( '0' ) ;
101+
102+ if ( num1AsSpan . Length < num2AsSpan . Length ) return - 1 ;
103+
104+ if ( num1AsSpan . Length > num2AsSpan . Length ) return 1 ;
105+
106+ var compareResult = num1AsSpan . CompareTo ( num2AsSpan , StringComparison . Ordinal ) ;
107+
108+ if ( compareResult != 0 ) return Math . Sign ( compareResult ) ;
109+
110+ if ( numValue2 . Length == numValue1 . Length ) return compareResult ;
111+
112+ return numValue2 . Length < numValue1 . Length ? - 1 : 1 ; // "033" < "33" === true
113+ }
36114 }
37115 }
38116}
0 commit comments