@@ -7,32 +7,118 @@ public sealed class NaturalStringComparer
7
7
{
8
8
public static IComparer < object > GetForProcessor ( )
9
9
{
10
- return Win32Helper . IsRunningOnArm ? new StringComparerArm64 ( ) : new StringComparerDefault ( ) ;
10
+ return new NaturalComparer ( StringComparison . CurrentCulture ) ;
11
11
}
12
12
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 > >
14
22
{
15
- public int Compare ( object a , object b )
16
- {
17
- return StringComparer . CurrentCulture . Compare ( a , b ) ;
18
- }
19
- }
23
+ private readonly StringComparison stringComparison ;
20
24
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
+ }
36
122
}
37
123
}
38
124
}
0 commit comments