StringBuiler değiştirilebilir bir nesnedir, F #, mümkün olduğunca değişmezlik kullanılmasını teşvik eder. Yani, mutasyondan ziyade dönüşüm kullanmalıdır. Bu F # içinde bir dize oluştururken StringBuilder için geçerli mi? F # değişmez bir alternatif var mı? Eğer öyleyse, bu alternatif verimli midir? Eğer yüksek performanslı sokması birleştirme ihtiyaç varsaBir StringBuilder'ı F # içinde yapmak için doğru bir şey mi kullanıyorsunuz?
cevap
Ben F # StringBuilder
kullanıyor olduğunu düşünüyorum sb.Append
'un geçerli örneğini StringBuilder
döndürdüğü gerçeği, fold
işleviyle kolayca kullanılabileceği anlamına gelir. Bu hala zorunlu olsa da (nesne mutasyona uğramıştır), StringBuilder
'a referans göstermediğinizde işlevsel stili ile makul ölçüde iyi uyuyor.
Ama aynı derecede, sadece dizeleri listesini oluşturmak ve String.concat
kullanarak bunları bir arada kullanabilirsiniz - bu StringBuilder
kullanmamak kadar etkilidir (daha yavaştır, ama çok - ve bitiştirmek dizeleri +
kullanmaktan daha belirgin hızlıdır)
Bu nedenle, listeler size benzer bir performans sağlar, ancak bunlar değişmezdir (ve eşzamanlılık ile iyi çalışırlar) - eğer algoritma dizgesi oluşturuyorsanız iyi bir uyum sağlarlar, çünkü yalnızca listenin ön tarafına dizeler ekleyebilirsiniz - Bu listelerde çok verimli bir işlemdir (ve sonra dizgeyi tersine çevirir). Ayrıca, liste ifadeler kullanarak size çok uygun bir sözdizimi verir:
// 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
kez F # Interactive içinde #time
kullanarak, oldukça hızlı, iş makinesinde ölçüldü - bu yayın oluşturma hızlı olacağını oldukça muhtemeldir ama bence oldukça temsili.
s2, 'String.concat' öncesindeki' List.rev' içeriyor mu? Yukarıda belirttiğiniz gibi, liste muhtemelen parçaların nasıl birleştirileceğini nasıl ters sırayla olacak şekilde oluşturulacaktır. – mydogisbox
ardından dize oluşturucu muhtemelen gitmek için doğru yoldur, ancak, dize oluşturucu daha işlevsel hale getirmek için yollar vardır. Genel olarak, eğer işlevsel bir programda mutabiliteye ihtiyacınız varsa, bunu yapmanın uygun yolu bunun için işlevsel bir sarıcı oluşturmaktır. F # içinde bu tipik olarak bir hesaplama ifadesi olarak ifade edilir. Bir dize oluşturucu hesaplama ifadesi here örneğidir.
Örnek Kullanım:
//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
Düzenleme
: Yukarıdaki hesaplama yeni bir ifade uygulanmasını yapılmış ve daha sonra bir Tomas'ın dört çözümlerinin sürümünü artı benim hesaplama ifade ve daha önce bağlanmış hesaplama ifadesini koştu. s3 ise sadece 6 kez alır s5 sürece zorunlu olduğu sürece 9 kez sürers1 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
dikkat edin. İşte
dize oluşturucu hesaplama ifade benim uygulamasıdır: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()
Zamanlayıcı fonksiyonu (her test 100 kez çalıştırılan unutmayın):
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
Sadece bir Reader özel bir durum https://github.com/fsharp/fsharpx/issues/201 –
@MauricioScheffer Gerçek uygulama üzerinden baktığımda, bu kötü olduğunu düşünüyorum StringBuilder sınıfının tüm performans avantajlarını kaybettiğinden, bir dize oluşturucu uygulama yolu. Ham 'StringBuilder' kullanmaya yakın performans özelliklerine sahip bir versiyon yaratmayı düşünüyorum. – mydogisbox
Tek performans isabeti, tamamen isteğe bağlı olan 'sprintf' kullanıyor. Başka bir performans sorunu görmüyorum (Reader hakkında konuşuyorum) –
Bir DList http://book.realworldhaskell.org/read/data-structures.html#data.dlist http://jackfoxy.com/f-data-structures/fsharpx-datastructures/#id35 –
kullanabilirsiniz. [Immutable string oluşturucu] (http: // stackoverflow) gönderdim.Daha önceki bir soruya yanıt olarak com/a/8346765/162396). Tomas'ın testi 18ms'de kullanıyor (diğer makineler için aynı zamanlamaları aldığım için makinelerimiz benzer olmalı). – Daniel
@MauricioScheffer DList ve basit listenin tersine çevrilmesi ile karşılaştırmanın ne olacağını bilmek isterim. Ben DList fonksiyon çağrıları da bazı maliyet olabilir sanırım ... –