@@ -7,32 +7,118 @@ 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 }
1212
13- private sealed class StringComparerArm64 : IComparer < object >
13+ /// <summary>
14+ /// Provides functionality to compare and sort strings in a natural (human-readable) order.
15+ /// </summary>
16+ /// <remarks>
17+ /// This class implements string comparison that respects the natural numeric order in strings,
18+ /// such as "file10" being ordered after "file2".
19+ /// It is designed to handle cases where alphanumeric sorting is required.
20+ /// </remarks>
21+ private sealed class NaturalComparer : IComparer < object ? > , IComparer < string ? > , IComparer < ReadOnlyMemory < char > >
1422 {
15- public int Compare ( object a , object b )
16- {
17- return StringComparer . CurrentCulture . Compare ( a , b ) ;
18- }
19- }
23+ private readonly StringComparison stringComparison ;
2024
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- }
25+ public NaturalComparer ( StringComparison stringComparison = StringComparison . Ordinal )
26+ {
27+ this . stringComparison = stringComparison ;
28+ }
29+
30+ public int Compare ( object ? x , object ? y )
31+ {
32+ if ( x == y ) return 0 ;
33+ if ( x == null ) return - 1 ;
34+ if ( y == null ) return 1 ;
35+
36+ return x switch
37+ {
38+ string x1 when y is string y1 => Compare ( x1 . AsSpan ( ) , y1 . AsSpan ( ) , stringComparison ) ,
39+ IComparable comparable => comparable . CompareTo ( y ) ,
40+ _ => StringComparer . FromComparison ( stringComparison ) . Compare ( x , y )
41+ } ;
42+ }
43+
44+ public int Compare ( string ? x , string ? y )
45+ {
46+ if ( ReferenceEquals ( x , y ) ) return 0 ;
47+ if ( x is null ) return - 1 ;
48+ if ( y is null ) return 1 ;
49+
50+ return Compare ( x . AsSpan ( ) , y . AsSpan ( ) , stringComparison ) ;
51+ }
52+
53+ public int Compare ( ReadOnlySpan < char > x , ReadOnlySpan < char > y )
54+ {
55+ return Compare ( x , y , stringComparison ) ;
56+ }
57+
58+ public int Compare ( ReadOnlyMemory < char > x , ReadOnlyMemory < char > y )
59+ {
60+ return Compare ( x . Span , y . Span , stringComparison ) ;
61+ }
62+
63+ public static int Compare ( ReadOnlySpan < char > x , ReadOnlySpan < char > y , StringComparison stringComparison )
64+ {
65+ var length = Math . Min ( x . Length , y . Length ) ;
66+
67+ for ( var i = 0 ; i < length ; i ++ )
68+ {
69+ if ( char . IsDigit ( x [ i ] ) && char . IsDigit ( y [ i ] ) )
70+ {
71+ var xOut = GetNumber ( x . Slice ( i ) , out var xNumAsSpan ) ;
72+ var yOut = GetNumber ( y . Slice ( i ) , out var yNumAsSpan ) ;
73+
74+ var compareResult = CompareNumValues ( xNumAsSpan , yNumAsSpan ) ;
75+
76+ if ( compareResult != 0 ) return compareResult ;
77+
78+ i = - 1 ;
79+ length = Math . Min ( xOut . Length , yOut . Length ) ;
80+
81+ x = xOut ;
82+ y = yOut ;
83+ continue ;
84+ }
85+
86+ var charCompareResult = x . Slice ( i , 1 ) . CompareTo ( y . Slice ( i , 1 ) , stringComparison ) ;
87+ if ( charCompareResult != 0 ) return charCompareResult ;
88+ }
89+
90+ return x . Length . CompareTo ( y . Length ) ;
91+ }
92+
93+ private static ReadOnlySpan < char > GetNumber ( ReadOnlySpan < char > span , out ReadOnlySpan < char > number )
94+ {
95+ var i = 0 ;
96+ while ( i < span . Length && char . IsDigit ( span [ i ] ) )
97+ {
98+ i ++ ;
99+ }
100+
101+ number = span . Slice ( 0 , i ) ;
102+ return span . Slice ( i ) ;
103+ }
104+
105+ private static int CompareNumValues ( ReadOnlySpan < char > numValue1 , ReadOnlySpan < char > numValue2 )
106+ {
107+ var num1AsSpan = numValue1 . TrimStart ( '0' ) ;
108+ var num2AsSpan = numValue2 . TrimStart ( '0' ) ;
109+
110+ if ( num1AsSpan . Length < num2AsSpan . Length ) return - 1 ;
111+
112+ if ( num1AsSpan . Length > num2AsSpan . Length ) return 1 ;
113+
114+ var compareResult = num1AsSpan . CompareTo ( num2AsSpan , StringComparison . Ordinal ) ;
115+
116+ if ( compareResult != 0 ) return Math . Sign ( compareResult ) ;
117+
118+ if ( numValue2 . Length == numValue1 . Length ) return compareResult ;
119+
120+ return numValue2 . Length < numValue1 . Length ? - 1 : 1 ; // "033" < "33" == true
121+ }
36122 }
37123 }
38124}
0 commit comments