1

私はグーグルで読んで、それを行うための「正しい」方法を見つけようとしていますが、SOで読んだすべての質問は完全に異なる答えを持っているようです。

これが私の問題の要点です。ファイルには、トリプル(a:string、b:string、c:Int64)のseqの型アノテーションがあります。f#に慣れていないので、型アノテーションの表現にまだ精通していません(または、さらに言えば、型シグネチャを理解することもできません)。aはファイル名、bは内部識別子、cはファイルの長さ(サイズ)を表す値です。baseconfigは、コードの前半の文字列です。

ignore(files 
    |> Seq.filter( fun(x,y,z) ->  y = baseconfig)  // used to filter only files we want
    |> Seq.fold( fun f n   -> 
        if( (fun (_,_,z) -> z) n > 50L*1024L*1024L) then
            zipfilex.Add((fun (z:string, _, _) -> z) n)
            printfn("Adding 50mb to zip")
            zipfilex.CommitUpdate()
            zipfilex.BeginUpdate()
            ("","",0L)
        else
            zipfilex.Add((fun (z, _, _) -> z) n)
            ("", "", (fun (_, _, z:Int64) -> z) n + (fun (_, _, z:Int64) -> z) f)
    ) ("","",0L)
    )

このコードのチャンクが行うことになっているのは、の各ファイルを反復処理しfiles、それをzipアーカイブに追加することです(実際には、後でコミットするためにリストに追加されます)。ファイルが50 MBを超えると、現在のファイルをコミットします。 zipアーカイブへの保留中のファイル。ファイルの追加は安価で、コミットは高価なので、バッチ処理することでコストを軽減しようとしています。

これまでのところ、コードはちょっと機能します...コミットされたファイルの150MBに近づいたときに取得したObjectDisposedExceptionを除いて。しかし、これがそのような操作を行う正しい方法であるかどうかはわかりません。型破りな使い方をしているような気がしSeq.foldますが、それでももっといい方法がわかりません。

ボーナスの質問:タプルから値をスナイプするためのより良い方法はありますか?fstとsndは2つの値のタプルに対してのみ機能し、私が行ったようにインライン化する代わりに独自の関数を定義できることを認識していますが、より良い方法があるはずです。

更新:以前のフォールドの試みでは、なぜInt64をアキュムレータとして使用できないのか理解できませんでした。重要な括弧が欠けていたことがわかりました。以下の少し単純なバージョン。また、すべてのクレイジーなタプル抽出を排除します。

ignore(foundoldfiles 
    |> Seq.filter( fun (x,y,z) ->  y = baseconfig) 
    |> Seq.fold( fun (a) (f,g,j)   -> 
        zipfilex.Add( f)
        if( a > 50L*1024L*1024L) then
            printfn("Adding 50mb to zip")
            zipfilex.CommitUpdate()
            zipfilex.BeginUpdate()
            0L
        else
             a + j
    ) 0L
    )

更新2:命令型の解決策を採用する必要があります。F#は、後続のステートメントでzipファイルを閉じた後、どういうわけかこのコードブロックを再入力しています。これはObjectDisposedExceptionを説明しています。それがどのように機能するのか、またはその理由はわかりません。

4

4 に答える 4

4

「汚い」命令スタイルの代替として、Seqチャンク用の一般的で再利用可能な関数を使用してモジュールを拡張できます。この関数は に少し似foldていますが、 を返すラムダを取りますoption<'State>。が返された場合None、新しいチャンクが開始され、それ以外の場合、要素は前のチャンクに追加されます。次に、エレガントなソリューションを作成できます。

files
|> Seq.filter(fun (x, y, z) ->  y = baseconfig) 
|> Seq.chunkBy(fun (x, y, z) sum -> 
     if sum + z > 50L*1024L*1024L then None
     else Some(sum + z)) 0L
|> Seq.iter(fun files ->
    zipfilex.BeginUpdate()
    for f, _, _ in files do zipfilex.Add(f)
    zipfilex.CommitUpdate())

関数の実装はchunkBy少し長くなります -IEnumerator直接使用する必要があり、再帰を使用して表現できます:

module Seq = 
  let chunkBy f initst (files:seq<_>) = 
    let en = files.GetEnumerator()
    let rec loop chunk st = seq {
      if not (en.MoveNext()) then
        if chunk <> [] then yield chunk
      else
        match f en.Current st with
        | Some(nst) -> yield! loop (en.Current::chunk) nst
        | None -> 
            yield chunk 
            yield! loop [en.Current] initst }
    loop [] initst
于 2011-04-15T16:15:21.857 に答える
2

を使用しても問題が解決するとは思いませんfold。不変の構造を構築するときに最も役立ちます。私の意見では、この場合、何をしようとしているのかがはっきりしなくなります。命令的な解決策はうまく機能します:

let mutable a = 0L
for (f, g, j) in foundoldfiles do
    if g = baseconfig then
        zipfilex.Add(f)
        if a > 50L * 1024L * 1024L then
            printfn "Adding 50mb to zip"
            zipfilex.CommitUpdate()
            zipfilex.BeginUpdate()
            a <- 0L
        else
            a <- a + j
于 2011-04-15T16:06:41.690 に答える
1

これが私の見解です:

let inline zip a b = a, b

foundoldfiles 
|> Seq.filter (fun (_, internalid, _) -> internalid = baseconfig)
|> zip 0L
||> Seq.fold (fun acc (filename, _, filesize) -> 
    zipfilex.Add filename
    let acc = acc + filesize
    if acc > 50L*1024L*1024L then
        printfn "Adding 50mb to zip"
        zipfilex.CommitUpdate ()
        zipfilex.BeginUpdate ()
        0L
    else acc)
|> ignore

いくつかのメモ:

  • ヘルパー関数は、zipオーバーヘッドなしで関数全体を通るクリーンなパイプラインを作成し、より複雑なシナリオでは、状態がファンクターの右側から左側にシフトされるため、型の推論に役立ちますfold(ただし、それは問題ではなく、助けにもなりません)。この特定のケースでは)
  • を使用し_て、必要のないタプルの要素をローカルで破棄すると、コードが読みやすくなります。
  • ignore余分な括弧で式全体をラップするのではなく、パイプライン処理するアプローチにより、コードが読みやすくなります
  • 単項関数の引数を括弧で囲むのは奇妙に見えます。非単項カリー化関数には括弧を使用できないため、単項関数に括弧を使用すると一貫性がなくなります。私のポリシーは、コンストラクター呼び出しとタプル関数呼び出しのために括弧を予約することです

編集: PSif( a > 50L*1024L*1024L) thenは正しくないロジックifです。アキュムレータと現在のファイルサイズを考慮する必要があります。たとえば、最初のファイルが >= 50MB の場合、if はトリガーされません。

于 2011-04-15T15:52:13.803 に答える
1

変更可能な変数と命令型ループが苦手な場合は、 GOTO関数型ループを使用してこれをいつでも書き直すことができます。

let rec loop acc = function
    | (file, id, size) :: files ->
        if id = baseconfig then
            zipfilex.Add file
            if acc > 50L*1024L*1024L then
                printfn "Adding 50mb to zip"
                zipfilex.CommitUpdate()
                zipfilex.BeginUpdate()
                loop 0L files
            else
                loop (acc + size) files
        else
            loop acc files
    | [] -> ()

loop 0L foundoldfiles

これの利点は、帰納的なケースが進むことができる 3 つの異なる方法と、各ケースでアキュムレータがどのように変換されるかを明示的に述べていることです (したがって、これを間違える可能性は低くなります - Daniel の for ループ バージョンのバグを目​​撃してください)。

baseconfig チェックを when 句に移動することもできます。

let rec loop acc = function
    | (file, id, size) :: files when id = baseconfig ->
        zipfilex.Add file
        if acc > 50L*1024L*1024L then
            printfn "Adding 50mb to zip"
            zipfilex.CommitUpdate()
            zipfilex.BeginUpdate()
            loop 0L files
        else
            loop (acc + size) files
    | _ :: files -> loop acc files
    | [] -> ()

loop 0L foundoldfiles
于 2011-04-15T20:19:47.437 に答える