I think that using StringBuilder
in F# is perfectly fine - the fact that sb.Append
returns the current instance of StringBuilder
means that it can be easily used with the fold
function. Even though this is still imperative (the object is mutated), it fits reasonably well with the functional style when you do not expose references to StringBuilder
.
But equally, you can just construct a list of strings and concatenate them using String.concat
- this is almost as efficient as using StringBuilder
(it is slower, but not much - and it is significantly faster than concatenating strings using +
)
So, lists give you similar performance, but they are immutable (and work well with concurrency etc.) - they would be a good fit if you were building string algorithmically, because you can just append strings to the front of the list - this is very efficient operation on lists (and then reverse the string). Also, using list expressions gives you a very convenient syntax:
// Concatenating strings using + (2.3 seconds)
let s1 = [ for i in 0 .. 25000 -> "Hello " ] |> Seq.reduce (+)
s1.Length
// Creating immutable list and using String.concat (5 ms)
let s2 = [ for i in 0 .. 25000 -> "Hello " ] |> String.concat ""
s2.Length
// Creating a lazy sequence and concatenating using StringBuilder & fold (5 ms)
let s3 =
seq { for i in 0 .. 25000 -> "Hello " }
|> Seq.fold(fun (sb:System.Text.StringBuilder) s ->
sb.Append(s)) (new System.Text.StringBuilder())
|> fun x -> x.ToString()
s3.Length
// Imperative solution using StringBuilder and for loop (1 ms)
let s4 =
( let sb = new System.Text.StringBuilder()
for i in 0 .. 25000 do sb.Append("Hello ") |> ignore
sb.ToString() )
s4.Length
The times were measured on my, fairly fast, work machine using #time
in F# Interactive - it is quite likely that it would be faster in release build, but I think they are fairly representative.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…