0

次の使用例を考えてみましょう:
2 つの db テーブルを並行して反復処理し、いずれかのテーブルの違いとギャップ/欠落レコードを見つけたいと考えています。1) テーブルの pk は Int ID フィールドです。2) テーブルは ID 順に読み込まれます。3) いずれかのテーブルからレコードが欠落している可能性があります (対応するシーケンス ギャップを伴う)。

遅延読み取りを使用して、各データベースに対して1回のパスでこれを行いたいと思います。(このプログラムの最初のバージョンでは、シーケンス オブジェクトとデータ リーダーを使用しています。残念ながら、各データベースに対して複数のパスを作成します)。

ペアごとのシーケンス処理を使用することを考え、反復内で Seq.skip を使用して、テーブル処理を同期させようとしました。ただし、I Seq.skip のオーバーヘッドが大きいため (フードの下で新しいシーケンスを作成する)、明らかにこれは非常に遅いため、大きなテーブル (たとえば 200k rec) では問題になる可能性があります。

これは一般的な設計パターン (さまざまなソースからの同時データ ストリームを比較する) であり、同様のプロジェクトへのフィードバック/コメント/リンクに関心があります。

コメントしたい人はいますか?

4

2 に答える 2

1

シーケンスを使用すると、遅延関数によってシーケンスにオーバーヘッドが追加されます。同じシーケンスで Seq.skip を何千回も呼び出すと、明らかに遅くなります。

Seq.zipまたはを使用Seq.map2して、一度に 2 つのシーケンスを処理できます。

> Seq.map2 (+) [1..3] [10..12];;
val it : seq<int> = seq [11; 13; 15]

Seq モジュールが十分でない場合は、独自の関数を作成する必要がある場合があります。あなたがやろうとしていることを理解しているかどうかはわかりませんが、このサンプル関数が役立つかもしれません:

let fct (s1: seq<_>) (s2: seq<_>) =
    use e1 = s1.GetEnumerator()
    use e2 = s2.GetEnumerator()
    let rec walk () =

        // do some stuff with the element of both sequences
        printfn "%d %d" e1.Current e2.Current

        if cond1 then // move in both sequences
            if e1.MoveNext() && e2.MoveNext() then walk ()
            else () // end of a sequence

        elif cond2 then // move to the next element of s1
            if e1.MoveNext() then walk()
            else () // end of s1

        elif cond3 then // move to the next element of s2
            if e2.MoveNext() then walk ()
            else () // end of s2

    // we need at least one element in each sequence
    if e1.MoveNext() && e2.MoveNext() then walk()

編集 :

前の関数は Seq モジュールの機能を拡張することを意図していたので、おそらくそれを高階関数にしたいと思うでしょう。ildjarn が言ったように、LazyList を使用するとコードがきれいになります。

let rec merge (l1: LazyList<_>) (l2: LazyList<_>) =
    match l1, l2 with
    | LazyList.Cons(h1, t1), LazyList.Cons(h2, t2) ->
        if h1 <= h2 then LazyList.cons h1 (merge t1 l2)
        else LazyList.cons h2 (merge l1 t2)
    | LazyList.Nil, l2 -> l2
    | _ -> l1

merge (LazyList.ofSeq [1; 4; 5; 7]) (LazyList.ofSeq [1; 2; 3; 6; 8; 9])

しかし、データの反復を処理から分離する必要があると思います。反復する高階関数を作成するのは良い考えです (最終的には、反復子関数のコードで変更可能な列挙子を使用しても問題ありません)。

于 2011-04-13T19:04:12.210 に答える
1

これが私の(完全にテストされていない)テイクで、両方のテーブルで1回のパスを実行しています:

let findDifferences readerA readerB =
    let idsA, idsB =
        let getIds (reader:System.Data.Common.DbDataReader) =
            reader |> LazyList.unfold (fun reader ->
                if reader.Read ()
                then Some (reader.GetInt32 0, reader)
                else None)
        getIds readerA, getIds readerB

    let onlyInA, onlyInB = ResizeArray<_>(), ResizeArray<_>()
    let rec impl a b =
        let inline handleOnlyInA idA as' = onlyInA.Add idA; impl as' b
        let inline handleOnlyInB idB bs' = onlyInB.Add idB; impl a bs'
        match a, b with
        | LazyList.Cons (idA, as'), LazyList.Cons (idB, bs') ->
                if   idA < idB then handleOnlyInA idA as'
                elif idA > idB then handleOnlyInB idB bs'
                else impl as' bs'
        | LazyList.Nil, LazyList.Nil  -> () // termination condition
        | LazyList.Cons (idA, as'), _ -> handleOnlyInA idA as'
        | _, LazyList.Cons (idB, bs') -> handleOnlyInB idB bs'
    impl idsA idsB
    onlyInA.ToArray (), onlyInB.ToArray ()

これは 2 つDataReaderの (テーブルごとに 1 つ) を取りint[]、それぞれのテーブルにのみ存在する ID を示す 2 つの を返します。intこのコードは、ID フィールドの型が序数 index であると想定しています0

また、このコードはF# PowerPackLazyListを使用しているため、まだ持っていない場合は取得する必要があります。.NET 4.0 をターゲットにしている場合は、ここでビルドしてホストしている .NET 4.0 バイナリを取得することを強くお勧めします。F# PowerPack サイトのバイナリは .NET 2.0 のみをターゲットにしており、VS2010 SP1 ではうまく動作しない場合があるためです。 (詳細については、このスレッドを参照してください: Problem with F# Powerpack. Method not found error )。

于 2011-04-13T21:51:22.750 に答える