on f5c1cb2 help sign in
182 lines 7.0 kB history raw
1// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
3namespace Microsoft.FSharp.Core
5 open System
6 open System.Text
7 open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators
8 open Microsoft.FSharp.Core.Operators
9 open Microsoft.FSharp.Core.Operators.Checked
10 open Microsoft.FSharp.Collections
11 open Microsoft.FSharp.Primitives.Basics
13 [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
14 [<RequireQualifiedAccess>]
15 module String =
16 [<Literal>]
17 /// LOH threshold is calculated from Internal.Utilities.Library.LOH_SIZE_THRESHOLD_BYTES,
18 /// and is equal to 80_000 / sizeof<char>
19 let LOH_CHAR_THRESHOLD = 40_000
21 [<CompiledName("Length")>]
22 let length (str:string) = if isNull str then 0 else str.Length
24 [<CompiledName("Concat")>]
25 let concat sep (strings : seq<string>) =
27 let concatArray sep (strings: string []) =
28 match length sep with
29 | 0 -> String.Concat strings
30 // following line should be used when this overload becomes part of .NET Standard (it's only in .NET Core)
31 //| 1 -> String.Join(sep.[0], strings, 0, strings.Length)
32 | _ -> String.Join(sep, strings, 0, strings.Length)
34 match strings with
35 | :? array<string> as arr ->
36 concatArray sep arr
38 | :? list<string> as lst ->
39 lst
40 |> List.toArray
41 |> concatArray sep
43 | _ ->
44 String.Join(sep, strings)
46 [<CompiledName("Iterate")>]
47 let iter (action : (char -> unit)) (str:string) =
48 if not (String.IsNullOrEmpty str) then
49 for i = 0 to str.Length - 1 do
50 action str.[i]
52 [<CompiledName("IterateIndexed")>]
53 let iteri action (str:string) =
54 if not (String.IsNullOrEmpty str) then
55 let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(action)
56 for i = 0 to str.Length - 1 do
57 f.Invoke(i, str.[i])
59 [<CompiledName("Map")>]
60 let map (mapping: char -> char) (str:string) =
61 if String.IsNullOrEmpty str then
62 String.Empty
63 else
64 let result = str.ToCharArray()
65 let mutable i = 0
66 for c in result do
67 result.[i] <- mapping c
68 i <- i + 1
70 new String(result)
72 [<CompiledName("MapIndexed")>]
73 let mapi (mapping: int -> char -> char) (str:string) =
74 let len = length str
75 if len = 0 then
76 String.Empty
77 else
78 let result = str.ToCharArray()
79 let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(mapping)
81 let mutable i = 0
82 while i < len do
83 result.[i] <- f.Invoke(i, result.[i])
84 i <- i + 1
86 new String(result)
88 [<CompiledName("Filter")>]
89 let filter (predicate: char -> bool) (str:string) =
90 let len = length str
92 if len = 0 then
93 String.Empty
95 elif len > LOH_CHAR_THRESHOLD then
96 // By using SB here, which is twice slower than the optimized path, we prevent LOH allocations
97 // and 'stop the world' collections if the filtering results in smaller strings.
98 // We also don't pre-allocate SB here, to allow for less mem pressure when filter result is small.
99 let res = StringBuilder()
100 str |> iter (fun c -> if predicate c then res.Append c |> ignore)
101 res.ToString()
103 else
104 // Must do it this way, since array.fs is not yet in scope, but this is safe
105 let target = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked len
106 let mutable i = 0
107 for c in str do
108 if predicate c then
109 target.[i] <- c
110 i <- i + 1
112 String(target, 0, i)
114 [<CompiledName("Collect")>]
115 let collect (mapping: char -> string) (str:string) =
116 if String.IsNullOrEmpty str then
117 String.Empty
118 else
119 let res = StringBuilder str.Length
120 str |> iter (fun c -> res.Append(mapping c) |> ignore)
121 res.ToString()
123 [<CompiledName("Initialize")>]
124 let init (count:int) (initializer: int-> string) =
125 if count < 0 then invalidArgInputMustBeNonNegative "count" count
126 let res = StringBuilder count
127 for i = 0 to count - 1 do
128 res.Append(initializer i) |> ignore
129 res.ToString()
131 [<CompiledName("Replicate")>]
132 let replicate (count:int) (str:string) =
133 if count < 0 then invalidArgInputMustBeNonNegative "count" count
135 let len = length str
136 if len = 0 || count = 0 then
137 String.Empty
139 elif len = 1 then
140 new String(str.[0], count)
142 elif count <= 4 then
143 match count with
144 | 1 -> str
145 | 2 -> String.Concat(str, str)
146 | 3 -> String.Concat(str, str, str)
147 | _ -> String.Concat(str, str, str, str)
149 else
150 // Using the primitive, because array.fs is not yet in scope. It's safe: both len and count are positive.
151 let target = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked (len * count)
152 let source = str.ToCharArray()
154 // O(log(n)) performance loop:
155 // Copy first string, then keep copying what we already copied
156 // (i.e., doubling it) until we reach or pass the halfway point
157 Array.Copy(source, 0, target, 0, len)
158 let mutable i = len
159 while i * 2 < target.Length do
160 Array.Copy(target, 0, target, i, i)
161 i <- i * 2
163 // finally, copy the remain half, or less-then half
164 Array.Copy(target, 0, target, i, target.Length - i)
165 new String(target)
168 [<CompiledName("ForAll")>]
169 let forall predicate (str:string) =
170 if String.IsNullOrEmpty str then
171 true
172 else
173 let rec check i = (i >= str.Length) || (predicate str.[i] && check (i+1))
174 check 0
176 [<CompiledName("Exists")>]
177 let exists predicate (str:string) =
178 if String.IsNullOrEmpty str then
179 false
180 else
181 let rec check i = (i < str.Length) && (predicate str.[i] || check (i+1))
182 check 0