-
Notifications
You must be signed in to change notification settings - Fork 852
Expand file tree
/
Copy pathstring.fs
More file actions
201 lines (153 loc) · 6.33 KB
/
string.fs
File metadata and controls
201 lines (153 loc) · 6.33 KB
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
namespace Microsoft.FSharp.Core
open System
open System.Text
open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators
open Microsoft.FSharp.Core.Operators
open Microsoft.FSharp.Core.Operators.Checked
open Microsoft.FSharp.Collections
open Microsoft.FSharp.Primitives.Basics
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
[<RequireQualifiedAccess>]
module String =
/// LOH threshold is calculated from Internal.Utilities.Library.LOH_SIZE_THRESHOLD_BYTES,
/// and is equal to 80_000 / sizeof<char>
[<Literal>]
let LOH_CHAR_THRESHOLD = 40_000
[<CompiledName("Length")>]
let length (str: string) =
if isNull str then 0 else str.Length
[<CompiledName("Concat")>]
let concat sep (strings: seq<string>) =
let concatArray sep (strings: string array) =
match length sep with
| 0 -> String.Concat strings
// following line should be used when this overload becomes part of .NET Standard (it's only in .NET Core)
//| 1 -> String.Join(sep.[0], strings, 0, strings.Length)
| _ -> String.Join(sep, strings, 0, strings.Length)
match strings with
| :? (string array) as arr -> concatArray sep arr
| :? (string list) as lst -> lst |> List.toArray |> concatArray sep
| _ -> String.Join(sep, strings)
[<CompiledName("Iterate")>]
let iter (action: char -> unit) (str: string) =
if not (String.IsNullOrEmpty str) then
for i = 0 to str.Length - 1 do
action str.[i]
[<CompiledName("IterateIndexed")>]
let iteri action (str: string) =
if not (String.IsNullOrEmpty str) then
let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt(action)
for i = 0 to str.Length - 1 do
f.Invoke(i, str.[i])
[<CompiledName("Map")>]
let map (mapping: char -> char) (str: string) =
if String.IsNullOrEmpty str then
String.Empty
else
let result = str.ToCharArray()
let mutable i = 0
for c in result do
result.[i] <- mapping c
i <- i + 1
String(result)
[<CompiledName("MapIndexed")>]
let mapi (mapping: int -> char -> char) (str: string) =
let len = length str
if len = 0 then
String.Empty
else
let result = str.ToCharArray()
let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt(mapping)
let mutable i = 0
while i < len do
result.[i] <- f.Invoke(i, result.[i])
i <- i + 1
String(result)
[<CompiledName("Filter")>]
let filter (predicate: char -> bool) (str: string) =
let len = length str
if len = 0 then
String.Empty
elif len > LOH_CHAR_THRESHOLD then
// By using SB here, which is twice slower than the optimized path, we prevent LOH allocations
// and 'stop the world' collections if the filtering results in smaller strings.
// We also don't pre-allocate SB here, to allow for less mem pressure when filter result is small.
let res = StringBuilder()
str
|> iter (fun c ->
if predicate c then
res.Append c |> ignore)
res.ToString()
else
// Must do it this way, since array.fs is not yet in scope, but this is safe
let target = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked len
let mutable i = 0
for c in str do
if predicate c then
target.[i] <- c
i <- i + 1
String(target, 0, i)
[<CompiledName("Collect")>]
let collect (mapping: char -> string) (str: string) =
if String.IsNullOrEmpty str then
String.Empty
else
let res = StringBuilder str.Length
str |> iter (fun c -> res.Append(mapping c) |> ignore)
res.ToString()
[<CompiledName("Initialize")>]
let init (count: int) (initializer: int -> string) =
if count < 0 then
invalidArgInputMustBeNonNegative "count" count
let res = StringBuilder count
for i = 0 to count - 1 do
res.Append(initializer i) |> ignore
res.ToString()
[<CompiledName("Replicate")>]
let replicate (count: int) (str: string) =
if count < 0 then
invalidArgInputMustBeNonNegative "count" count
let len = length str
if len = 0 || count = 0 then
String.Empty
elif len = 1 then
String(str.[0], count)
elif count <= 4 then
match count with
| 1 -> str
| 2 -> String.Concat(str, str)
| 3 -> String.Concat(str, str, str)
| _ -> String.Concat(str, str, str, str)
else
// Using the primitive, because array.fs is not yet in scope. It's safe: both len and count are positive.
let target =
Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked (len * count)
let source = str.ToCharArray()
// O(log(n)) performance loop:
// Copy first string, then keep copying what we already copied
// (i.e., doubling it) until we reach or pass the halfway point
Array.Copy(source, 0, target, 0, len)
let mutable i = len
while i * 2 < target.Length do
Array.Copy(target, 0, target, i, i)
i <- i * 2
// finally, copy the remain half, or less-then half
Array.Copy(target, 0, target, i, target.Length - i)
String(target)
[<CompiledName("ForAll")>]
let forall predicate (str: string) =
if String.IsNullOrEmpty str then
true
else
let rec check i =
(i >= str.Length) || (predicate str.[i] && check (i + 1))
check 0
[<CompiledName("Exists")>]
let exists predicate (str: string) =
if String.IsNullOrEmpty str then
false
else
let rec check i =
(i < str.Length) && (predicate str.[i] || check (i + 1))
check 0