usar remove practices once modified how created como best f# immutability stringbuilder

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?

Un fragmento


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.