StringBuiler 是一个可变对象,F# 鼓励尽可能使用不变性。所以应该使用转换而不是突变。在 F# 中构建字符串时,这是否适用于 StringBuilder?是否有 F# 不可变的替代方案?如果是这样,这种替代方案是否有效?
2 回答
我认为StringBuilder
在 F# 中使用非常好——sb.Append
返回当前实例的事实StringBuilder
意味着它可以很容易地与fold
函数一起使用。尽管这仍然是必要的(对象已发生变异),但当您不公开对StringBuilder
.
但同样,您可以构造一个字符串列表并使用连接它们String.concat
- 这几乎和使用一样有效StringBuilder
(它更慢,但不多 - 它比使用连接字符串快得多+
)
所以,列表给你类似的性能,但它们是不可变的(并且可以很好地处理并发等)——如果你在算法上构建字符串,它们将是一个很好的选择,因为你可以将字符串附加到列表的前面——这是对列表进行非常有效的操作(然后反转字符串)。此外,使用列表表达式为您提供了一种非常方便的语法:
// 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
时间是在我在 F# Interactive 中使用的相当快的工作机器上测量的#time
- 它很可能在发布版本中会更快,但我认为它们具有相当的代表性。
如果您需要高性能的字符串连接,那么字符串生成器可能是正确的方法,但是,有一些方法可以使字符串生成器更实用。一般来说,如果您需要函数式程序中的可变性,那么执行此操作的适当方法是为其创建函数式包装器。在 F# 中,这通常表示为计算表达式。这里有一个字符串生成器计算表达式的示例。
示例用法:
//Create a function which builds a string from an list of bytes
let bytes2hex (bytes : byte []) =
string {
for byte in bytes -> sprintf "%02x" byte
} |> build
//builds a string from four strings
string {
yield "one"
yield "two"
yield "three"
yield "four"
} |> build
编辑:我对上述计算表达式做了一个新的实现,然后运行了 Tomas 的四个解决方案的发布版本,加上我的计算表达式和我之前链接的计算表达式。
s1 elapsed Time: 128150 ms //concatenation
s2 elapsed Time: 459 ms //immutable list + String.concat
s3 elapsed Time: 354 ms //lazy sequence and concatenating using StringBuilder & fold
s4 elapsed Time: 39 ms //imperative
s5 elapsed Time: 235 ms //my computation expression
s6 elapsed Time: 334 ms //the linked computation expression
请注意,s3 需要的时间是命令式的 9 倍,而 s5 只需要 6 倍。
这是我对字符串生成器计算表达式的实现:
open System.Text
type StringBuilderUnion =
| Builder of StringBuilder
| StringItem of string
let build = function | Builder(x) -> string x | StringItem(x) -> string x
type StringBuilderCE () =
member __.Yield (txt : string) = StringItem(txt)
member __.Yield (c : char) = StringItem(c.ToString())
member __.Combine(f,g) = Builder(match f,g with
| Builder(F), Builder(G) ->F.Append(G.ToString())
| Builder(F), StringItem(G)->F.Append(G)
| StringItem(F),Builder(G) ->G.Insert(0, F)
| StringItem(F),StringItem(G)->StringBuilder(F).Append(G))
member __.Delay f = f()
member __.Zero () = StringItem("")
member __.For (xs : 'a seq, f : 'a -> StringBuilderUnion) =
let sb = StringBuilder()
for item in xs do
match f item with
| StringItem(s)-> sb.Append(s)|>ignore
| Builder(b)-> sb.Append(b.ToString())|>ignore
Builder(sb)
let builder1 = new StringBuilderCE ()
定时器功能(注意每个测试运行 100 次):
let duration f =
System.GC.Collect()
let timer = new System.Diagnostics.Stopwatch()
timer.Start()
for _ in 1..100 do
f() |> ignore
printfn "elapsed Time: %i ms" timer.ElapsedMilliseconds