2013-09-03 2 views
23

StringBuilerは可変オブジェクトです。F#は、不変性を可能な限り使用することを奨励します。したがって、突然変異ではなく形質転換を使うべきです。 F#で文字列を作成する場合、これはStringBuilderに適用されますか?そこにF#不変の代替はありますか?もしそうなら、この代わりに効率的な方法がありますか?F#でStringBuilderを使用するのは正しいことですか?

A snippet

+1

DLISTを使用できます。http://book.realworldhaskell.org/read/data-structures.html#data.dlist http://jackfoxy.com/f-data-structures/fsharpx-datastructures/#id35 –

+0

私は[不変の文字列ビルダー](http:// stackoverflow。com/a/8346765/162396)を参照してください。 Tomasのテストは18msで実行されます(他のバージョンでも同じタイミングが得られるため、マシンは似ているはずです)。 – Daniel

+0

@MauricioScheffer私は、DListと単純なリストとの比較をどのようにするのかを知りたいと思っています。私は、DListの関数呼び出しにはいくらかのコストもかかっていると思われます... –

答えて

37

私はF#でStringBuilderを使用することがあると思います完全に正確 - 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#インタラクティブで#timeを使用して、私の、かなり高速で、作業機械で測定した - それはリリースビルドで速くなることは非常に可能性があり、私は彼らがかなり代表的だと思う。

+0

s2は 'String.concat'の前に' List.rev'を入れていますか?あなたが上記のように、リストはおそらくそれらが連結したいと思う方法の逆順になるように構築されるでしょう。 – mydogisbox

7

あなたは、高性能刺さ連結の必要性を持っている場合は、その文字列ビルダは、おそらく行くための正しい方法である、しかし、文字列ビルダをより機能的にする方法があります。一般に、機能的なプログラムで変更可能性が必要な場合、これを実行する適切な方法は、機能的なラッパーを作成することです。 F#では、これは通常、計算式として表されます。文字列ビルダ計算式hereの例があります。

使用例:

//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 

編集: 私は上記の計算式の新しい実装を作った後、トーマス4つのソリューションのリリースバージョンに加えて、私の計算式と、私は以前にリンクされ、計算式を実行しました。

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 
+0

リーダーの特別な場合https://github.com/fsharp/fsharpx/issues/201 –

+1

@MauricioScheffer実際の実装を見てみると、これは貧弱だと思いますそれは 'StringBuilder'クラスのすべてのパフォーマンス上の利点を失うので、文字列ビルダーを実装する方法です。私は、生の 'StringBuilder'を使用することに近いパフォーマンス特性を持つバージョンを作成しようとしています。 – mydogisbox

+0

唯一のパフォーマンスヒットは 'sprintf'です。これは完全にオプションです。 –

関連する問題