remove - ¿Es correcto usar un StringBuilder en F#?
stringbuilder list c# (2)
Si necesita una concatenación de sting de alto rendimiento, entonces el generador de cadenas es probablemente el camino correcto, sin embargo, hay formas de hacer que el generador de cadenas sea más funcional. En general, si necesita la mutabilidad en un programa funcional, la forma adecuada de hacerlo es crear un contenedor funcional para él. En F #, esto se expresa típicamente como una expresión de cálculo. Hay un ejemplo de una expresión de cálculo del generador de cadenas here .
Ejemplo de uso:
//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
Edición: hice una nueva implementación de la expresión de cálculo anterior y luego ejecuté una versión de lanzamiento de las cuatro soluciones de Tomas más mi expresión de cálculo y la expresión de cálculo que vinculé previamente.
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
Observe que s3 tarda 9 veces más que el imperativo, mientras que s5 solo toma 6 veces más.
Aquí está mi implementación de la expresión de cálculo del generador de cadenas:
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 ()
Función de temporizador (tenga en cuenta que cada prueba se ejecuta 100 veces):
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
StringBuiler es un objeto mutable, F # fomenta el empleo de la inmutabilidad tanto como sea posible. Así que uno debería usar la transformación en lugar de la mutación. ¿Se aplica esto a StringBuilder cuando se trata de construir una cadena en F #? ¿Hay una alternativa inmutable de F # a ella? Si es así, ¿es esta alternativa tan eficiente?
Creo que usar StringBuilder
en F # es perfectamente sb.Append
: el hecho de que sb.Append
devuelva la instancia actual de StringBuilder
significa que se puede usar fácilmente con la función de fold
. Aunque esto todavía es imperativo (el objeto está mutado), se ajusta razonablemente bien con el estilo funcional cuando no se exponen referencias a StringBuilder
.
Pero igualmente, puedes construir una lista de cadenas y concatenarlas usando String.concat
, esto es casi tan eficiente como usar StringBuilder
(es más lento, pero no mucho, y es significativamente más rápido que concatenar cadenas con +
)
Por lo tanto, las listas le proporcionan un rendimiento similar, pero son inmutables (y funcionan bien con la concurrencia, etc.). operación muy eficiente en las listas (y luego revertir la cadena). Además, usar expresiones de lista te da una sintaxis muy conveniente:
// 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
Los tiempos se midieron en mi máquina de trabajo, bastante rápida, utilizando #time
en F # Interactivo - es muy probable que sea más rápido en la versión de lanzamiento, pero creo que son bastante representativos.