3 people like it.
Like the snippet!
Natural Sort Order
Sort strings as file managers usually do, treating numeric substrings as numbers.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
|
open System
open System.Text.RegularExpressions
module NaturalOrder =
module private Impl =
let regex = Regex ("([0-9]+)", RegexOptions.Compiled)
let trimLeadingZeros (s: string) =
s.TrimStart '0'
let toChars (s: string) =
s.ToCharArray()
let split text =
text
|> regex.Split
|> Seq.filter (fun s -> s.Length > 0)
|> Seq.toArray
let compareStrings (s1: string) (s2: string) =
// each string is either all letters or all numbers
let isNumeric1 = Char.IsDigit s1.[0]
let isNumeric2 = Char.IsDigit s2.[0]
// If we have a string and a number, the number comes first. When we have
// two strings, compare them normally. The tricky case is two numbers.
match isNumeric1, isNumeric2 with
| true, false -> -1
| false, true -> 1
| false, false -> String.Compare (s1, s2, true)
| true, true ->
// leading zeros will trip us up, get rid of them
let n1, n2 = trimLeadingZeros s1, trimLeadingZeros s2
if n1.Length < n2.Length then -1
elif n2.Length < n1.Length then 1
else
// compare digit-by-digit
let chars1, chars2 = toChars n1, toChars n2
let result =
chars2
|> Seq.zip chars1
|> Seq.tryPick (fun (c1, c2) ->
if c1 < c2 then Some -1
elif c2 < c1 then Some 1
else None)
match result with
| Some i -> i
| None -> 0
type Pair = {
Name: string
Pieces: string[]
}
open Impl
/// Sort a sequence of strings in natural order.
let sort names =
names
|> Seq.map (fun name -> { Name = name; Pieces = split name })
|> Seq.sortWith (fun p1 p2 -> Array.compareWith compareStrings p1.Pieces p2.Pieces)
|> Seq.map (fun pair -> pair.Name)
let files =
[
"VisualStudio.150x150.contrast-black_scale-100.png"
"VisualStudio.150x150.contrast-black_scale-140.png"
"VisualStudio.150x150.contrast-black_scale-180.png"
"VisualStudio.150x150.contrast-black_scale-80.png"
"Blend.150x150.contrast-black_scale-100.png"
"Blend.150x150.contrast-black_scale-140.png"
"Blend.150x150.contrast-black_scale-180.png"
"Blend.150x150.contrast-black_scale-80.png"
"Blend.70x70.contrast-black_scale-100.png"
"Blend.70x70.contrast-black_scale-140.png"
"Blend.70x70.contrast-black_scale-180.png"
"Blend.70x70.contrast-black_scale-80.png"
"VisualStudio.70x70.contrast-black_scale-100.png"
"VisualStudio.70x70.contrast-black_scale-140.png"
"VisualStudio.70x70.contrast-black_scale-180.png"
"VisualStudio.70x70.contrast-black_scale-80.png"
]
let sorted =
files
|> Seq.sort
|> Seq.toArray
(*
val sorted : string [] =
[|"Blend.150x150.contrast-black_scale-100.png";
"Blend.150x150.contrast-black_scale-140.png";
"Blend.150x150.contrast-black_scale-180.png";
"Blend.150x150.contrast-black_scale-80.png";
"Blend.70x70.contrast-black_scale-100.png";
"Blend.70x70.contrast-black_scale-140.png";
"Blend.70x70.contrast-black_scale-180.png";
"Blend.70x70.contrast-black_scale-80.png";
"VisualStudio.150x150.contrast-black_scale-100.png";
"VisualStudio.150x150.contrast-black_scale-140.png";
"VisualStudio.150x150.contrast-black_scale-180.png";
"VisualStudio.150x150.contrast-black_scale-80.png";
"VisualStudio.70x70.contrast-black_scale-100.png";
"VisualStudio.70x70.contrast-black_scale-140.png";
"VisualStudio.70x70.contrast-black_scale-180.png";
"VisualStudio.70x70.contrast-black_scale-80.png"|]*)
let naturallySorted =
files
|> NaturalOrder.sort
|> Seq.toArray
(*
val naturallySorted : string [] =
[|"Blend.70x70.contrast-black_scale-80.png";
"Blend.70x70.contrast-black_scale-100.png";
"Blend.70x70.contrast-black_scale-140.png";
"Blend.70x70.contrast-black_scale-180.png";
"Blend.150x150.contrast-black_scale-80.png";
"Blend.150x150.contrast-black_scale-100.png";
"Blend.150x150.contrast-black_scale-140.png";
"Blend.150x150.contrast-black_scale-180.png";
"VisualStudio.70x70.contrast-black_scale-80.png";
"VisualStudio.70x70.contrast-black_scale-100.png";
"VisualStudio.70x70.contrast-black_scale-140.png";
"VisualStudio.70x70.contrast-black_scale-180.png";
"VisualStudio.150x150.contrast-black_scale-80.png";
"VisualStudio.150x150.contrast-black_scale-100.png";
"VisualStudio.150x150.contrast-black_scale-140.png";
"VisualStudio.150x150.contrast-black_scale-180.png"|]
*)
|
namespace System
namespace System.Text
namespace System.Text.RegularExpressions
val private regex : Regex
Full name: Script.NaturalOrder.Impl.regex
Multiple items
type Regex =
new : pattern:string -> Regex + 1 overload
member GetGroupNames : unit -> string[]
member GetGroupNumbers : unit -> int[]
member GroupNameFromNumber : i:int -> string
member GroupNumberFromName : name:string -> int
member IsMatch : input:string -> bool + 1 overload
member Match : input:string -> Match + 2 overloads
member Matches : input:string -> MatchCollection + 1 overload
member Options : RegexOptions
member Replace : input:string * replacement:string -> string + 5 overloads
...
Full name: System.Text.RegularExpressions.Regex
--------------------
Regex(pattern: string) : unit
Regex(pattern: string, options: RegexOptions) : unit
type RegexOptions =
| None = 0
| IgnoreCase = 1
| Multiline = 2
| ExplicitCapture = 4
| Compiled = 8
| Singleline = 16
| IgnorePatternWhitespace = 32
| RightToLeft = 64
| ECMAScript = 256
| CultureInvariant = 512
Full name: System.Text.RegularExpressions.RegexOptions
field RegexOptions.Compiled = 8
val private trimLeadingZeros : s:string -> string
Full name: Script.NaturalOrder.Impl.trimLeadingZeros
val s : string
Multiple items
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = String
Full name: Microsoft.FSharp.Core.string
String.TrimStart([<ParamArray>] trimChars: char []) : string
val private toChars : s:string -> char []
Full name: Script.NaturalOrder.Impl.toChars
String.ToCharArray() : char []
String.ToCharArray(startIndex: int, length: int) : char []
val private split : text:string -> string []
Full name: Script.NaturalOrder.Impl.split
val text : string
Regex.Split(input: string) : string []
Regex.Split(input: string, count: int) : string []
Regex.Split(input: string, count: int, startat: int) : string []
module Seq
from Microsoft.FSharp.Collections
val filter : predicate:('T -> bool) -> source:seq<'T> -> seq<'T>
Full name: Microsoft.FSharp.Collections.Seq.filter
property String.Length: int
val toArray : source:seq<'T> -> 'T []
Full name: Microsoft.FSharp.Collections.Seq.toArray
val private compareStrings : s1:string -> s2:string -> int
Full name: Script.NaturalOrder.Impl.compareStrings
val s1 : string
val s2 : string
val isNumeric1 : bool
type Char =
struct
member CompareTo : value:obj -> int + 1 overload
member Equals : obj:obj -> bool + 1 overload
member GetHashCode : unit -> int
member GetTypeCode : unit -> TypeCode
member ToString : unit -> string + 1 overload
static val MaxValue : char
static val MinValue : char
static member ConvertFromUtf32 : utf32:int -> string
static member ConvertToUtf32 : highSurrogate:char * lowSurrogate:char -> int + 1 overload
static member GetNumericValue : c:char -> float + 1 overload
...
end
Full name: System.Char
Char.IsDigit(c: char) : bool
Char.IsDigit(s: string, index: int) : bool
val isNumeric2 : bool
Multiple items
type String =
new : value:char -> string + 7 overloads
member Chars : int -> char
member Clone : unit -> obj
member CompareTo : value:obj -> int + 1 overload
member Contains : value:string -> bool
member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
member EndsWith : value:string -> bool + 2 overloads
member Equals : obj:obj -> bool + 2 overloads
member GetEnumerator : unit -> CharEnumerator
member GetHashCode : unit -> int
...
Full name: System.String
--------------------
String(value: nativeptr<char>) : unit
String(value: nativeptr<sbyte>) : unit
String(value: char []) : unit
String(c: char, count: int) : unit
String(value: nativeptr<char>, startIndex: int, length: int) : unit
String(value: nativeptr<sbyte>, startIndex: int, length: int) : unit
String(value: char [], startIndex: int, length: int) : unit
String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: Text.Encoding) : unit
String.Compare(strA: string, strB: string) : int
String.Compare(strA: string, strB: string, comparisonType: StringComparison) : int
String.Compare(strA: string, strB: string, ignoreCase: bool) : int
String.Compare(strA: string, strB: string, ignoreCase: bool, culture: Globalization.CultureInfo) : int
String.Compare(strA: string, strB: string, culture: Globalization.CultureInfo, options: Globalization.CompareOptions) : int
String.Compare(strA: string, indexA: int, strB: string, indexB: int, length: int) : int
String.Compare(strA: string, indexA: int, strB: string, indexB: int, length: int, comparisonType: StringComparison) : int
String.Compare(strA: string, indexA: int, strB: string, indexB: int, length: int, ignoreCase: bool) : int
String.Compare(strA: string, indexA: int, strB: string, indexB: int, length: int, culture: Globalization.CultureInfo, options: Globalization.CompareOptions) : int
String.Compare(strA: string, indexA: int, strB: string, indexB: int, length: int, ignoreCase: bool, culture: Globalization.CultureInfo) : int
val n1 : string
val n2 : string
val chars1 : char []
val chars2 : char []
val result : int option
val zip : source1:seq<'T1> -> source2:seq<'T2> -> seq<'T1 * 'T2>
Full name: Microsoft.FSharp.Collections.Seq.zip
val tryPick : chooser:('T -> 'U option) -> source:seq<'T> -> 'U option
Full name: Microsoft.FSharp.Collections.Seq.tryPick
val c1 : char
val c2 : char
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
val i : int
type private Pair =
{Name: string;
Pieces: string [];}
Full name: Script.NaturalOrder.Impl.Pair
Pair.Name: string
Pair.Pieces: string []
module Impl
from Script.NaturalOrder
val sort : names:seq<string> -> seq<string>
Full name: Script.NaturalOrder.sort
Sort a sequence of strings in natural order.
val names : seq<string>
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>
Full name: Microsoft.FSharp.Collections.Seq.map
val name : string
val sortWith : comparer:('T -> 'T -> int) -> source:seq<'T> -> seq<'T>
Full name: Microsoft.FSharp.Collections.Seq.sortWith
val p1 : Pair
val p2 : Pair
type Array =
member Clone : unit -> obj
member CopyTo : array:Array * index:int -> unit + 1 overload
member GetEnumerator : unit -> IEnumerator
member GetLength : dimension:int -> int
member GetLongLength : dimension:int -> int64
member GetLowerBound : dimension:int -> int
member GetUpperBound : dimension:int -> int
member GetValue : [<ParamArray>] indices:int[] -> obj + 7 overloads
member Initialize : unit -> unit
member IsFixedSize : bool
...
Full name: System.Array
val compareWith : comparer:('T -> 'T -> int) -> array1:'T [] -> array2:'T [] -> int
Full name: Microsoft.FSharp.Collections.Array.compareWith
val pair : Pair
val files : string list
Full name: Script.files
val sorted : string []
Full name: Script.sorted
val sort : source:seq<'T> -> seq<'T> (requires comparison)
Full name: Microsoft.FSharp.Collections.Seq.sort
val naturallySorted : string []
Full name: Script.naturallySorted
module NaturalOrder
from Script
More information