高速タグ検索のアルゴリズム


16

問題は次のとおりです。

  • 単純なエンティティEのセットがあり、各エンティティにはタグTのセットが添付されています。各エンティティには、任意の数のタグを付けることができます。エンティティの総数は1億に近く、タグの総数は約5000です。

したがって、初期データは次のようになります。

E1 - T1, T2, T3, ... Tn
E2 - T1, T5, T100, ... Tk
..
Ez - T10, T12, ... Tl

この初期データが更新されることはほとんどありません。

  • どういうわけか、私のアプリは次のようなタグで論理式を生成します。

    T1&T2&T3 | (T5&!T6)

  • 私がする必要があるのは、与えられた表現に一致するエンティティの数を計算することです(注-エンティティではなく、単に数)。もちろん、これは完全に正確ではありません。

私が今持っているのは、単一のスレッドで5〜10秒の実行時間を与える単純なメモリ内テーブル検索です。

私は興味がありますが、このようなものを処理する効率的な方法はありますか?どのアプローチをお勧めしますか?このための一般的なアルゴリズムやデータ構造はありますか?

更新

要求に応じて少し説明します。

  1. Tオブジェクトは実際には比較的短い定数文字列です。しかし、実際には問題ではありません-常にいくつかのIDを割り当てて整数を操作できます。
  2. 間違いなくソートできます。

1
であるT1ため、同じオブジェクトの参照E1E2など?
Reactgular

タグはどのように比較できますか?タグT2 < T3は常に正しいようにソートできますか?
Reactgular

タグはバイナリですか?つまりはT1どちらかであるtruefalse特定のためEの入力に基づいて可変としませんか?(すなわちModel = "V5")またはT1変数式はModel = <input>
ボブソン

回答:


4

私は、自己結合を使用して参照エンティティと 参照カテゴリのEntityCategory間にリンクテーブルを持つSQLでこれを行います:eidcid

    select count(ec1.eid)
    from EntityCategory ec1 
    left join EntityCategory ec2 on ec1.eid=ec2.eid 
    left join EntityCategory ec3 on ec1.eid=ec3.eid 
    ...
    where 
      ec1.cid={categoryId1} and 
      ec2.cid={categoryId2} and
      ec3.cid={categoryId3} ...

1
+1、これは古典的なDB領域です。もう1つの答えには、手動でコーディングする方法についての合理的なアイデアがありますが、それは最後の手段です。
MSalters

また、これを解決する手法としてsqlを選択します。ほとんどのデータベースは、これらのアルゴリズムに対してかなり最適化されています:)
winkbrace

3

この回答を書いた後、おそらく「広すぎる」という質問にフラグを立てる必要があります。さまざまな戦略について長年話し合うことができます。最終的には、データでベンチマークを実行する必要があります。

各タグは整数で効率的に表すことができます。各エンティティにはタグのセットがあります。正しいセットの実装を選択することは重要です。Bツリーとソートされた配列の両方が可能です。このセットでは、メンバーシップテストのみを行います。どちらの構造もO(log t)(エンティティごとにtタグを使用)でこれを行うため、配列の密度が高いため配列を優先します。

O(n・log t・p)操作ですべてのエンティティをフィルター処理できるようになりました。ここで、pは述語決定ツリーの平均パス長です。この決定木は、決定に迅速に到達できるように順序付けることができます。統計データがなければ、共通の部分式を除外することしかできません。

エンティティが検索される順序は実際には重要ではありません。一方、すべてのインデックス0にあるエンティティにi特定のタグを付け、残りには付けないようにソートすることは有利です。これにより、この特定のタグを検索する際のnが削減されます(決定ツリーでは、これが最初のテストになります)。これは、複数のレベルに拡大するが、この複雑ものと取ることができるO(2 KとメモリをKレベル。複数のレベルでは、ゲインが最も高いタグを最初に決定する必要があります。ゲインとは、検索する必要のないエンティティの数とそれらを破棄する確率です。ゲインは、50:50の機会、またはエンティティの50%がこの特定のタグを持っている場合に最大になります。これにより、アクセスパターンが不明な場合でも最適化できます。

以下のためのすべてのエンティティと1セット-あなたはまた、各タグにより、インデックスのエンティティが使用したセット作成することができT1、ために次をT2。(空間と時間)明らかな最適化は、セットがよりすべての要素の半分以上が含まれている場合に停止し、そしてそれらの要素保存することですしていないこのように、すべてのタグのインデックスを構築すると、以下になります-このタグを持っているの½ · n · tと(スペース合計でtタグ)。補完セットを保存すると、他の最適化がより難しくなることに注意してください。繰り返しますが、私はセットの配列を(ソート)します。

整数範囲でエンティティを表す場合、連続範囲の開始メンバーと終了メンバーのみを保存することにより、インデックスセットに使用されるスペースを圧縮できます。実装上、これはおそらくエントリが範囲バウンドであるか通常のエントリであるかを示すために高ビットで行われます。

インデックスセット(およびタグの統計情報)があれば、述語を最適化して、ありそうもないプロパティが最初にテストされるようにすることができます(フェイルファースト戦略)。つまり、T1一般的でT2まれな場合は、T1 & T2すべてのT2インデックスセットエントリを反復処理し、各要素のをテストして述語を評価する必要がありますT1

ソートされた配列を使用してインデックスセットを実装する場合、多くの評価ステップをマージ操作として実装できます。とリストT1 & T2を取得し、より大きな入力のサイズにターゲット配列を割り当て、両方の入力が空になるまで次のアルゴリズムを実行することを意味します:If 、then (破棄)。もし、その後。両方のヘッドが等しい場合、標的配列への上、その番号をコピーし、すべての3つのポインタ(インクリメントし、ターゲットを)。述語がの場合、要素は破棄されませんが、より小さい要素がコピーされます。フォームの述語はまた、マージ戦略を使用して実装することができるが、又はできません。T1T2T1[0] < T2[0]T1++T1[0] > T2[0]T2++T1T2T1 | T2T1 & ¬T2¬T1T1 | ¬T2

これは、述語決定ツリーの順序付け時に考慮する必要があります。補数は&、最終カウントが決定され、実際の要素を調べる必要がない場合、のRHSで、または最後に発生する必要があります。

インデックスセットを使用せずに、各スレッドはエンティティの一部をフィルター処理し、述語に一致する要素の数を返すことができます。インデックスセットを使用する場合、各スレッドには決定ツリーの1つのノードが割り当てられます。順序付けられたセットに対応する2つの入力ストリームを受け取り、マージされたストリームを返します。デシジョンツリーの各ノードには、その部分式を満たすすべてのエンティティを表す対応するセットがあり、セットの順序により、それらをマージするためにセット全体を一度に知る必要がないことに注意してください。 。

インデックスセットのマージやエンティティのリストのフィルタリングなど、さまざまな戦略をある程度組み合わせることができます。フィルタリングのパフォーマンスは非常に予測可能です。クエリが非常に限定的であるため、インデックスセットを使用すると検索スペースが大幅に減少する場合、マージ操作の方が優れている可能性があります。多くの大きな入力セットをマージすると、ブルートフォース検索よりもパフォーマンスが大幅に低下する可能性があることに注意することが重要です。非常に最適化されたアルゴリズムは、入力サイズ、クエリ構造、統計指標に基づいて適切な戦略を選択します。

余談ですが、部分的な結果のキャッシングは、初期クエリの実行速度を上げなくても、同様のクエリが将来実行されることが予想される場合に役立ちます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.