これは、非常に大量のパターンがある場合にうまく機能すると私が信じている解決策です。たった 10k ではやり過ぎかもしれませんし、それを実装することは比較的多くの作業を意味しますが、それでも興味があるかもしれません。
基本的な考え方は、パターンのサブストリングをパターン ID にマップする逆索引を作成することです。まず、各パターンに ID を取得します。
1: what is *
2: where is *
3: do * need to
etc.
次に、転置インデックスを作成します。最も単純なケースでは、パターンをトークンに分割し、各トークンをそれが出現するパターン ID のリストにマップします。トークンとして何を定義するかは柔軟に決めることができますが、1 つの方法は、空白で区切られたすべての単語が1トークンです。だからここにインデックスがあります:
what -> 1
is -> 1,2
where -> 2
do -> 3
need -> 3
to -> 3
次に、ユーザーから入力文字列を取得したら、それをトークンに分割し、インデックスで検索します。インデックスから取得したすべてのパターン ID を結合します。例:
INPUT: what is something?
TOKENS:
what -> 1
is -> 1,2
something -> n/a
各トークンのパターン ID を取得し、各 ID の頻度をカウントする一時データ構造 (ハッシュなど) に配置します (例: a std::unordered_map<id_type,std::size_t>
)。
次に、これを頻度で並べ替えて、ルール 1 が 2 回検出され、ルール 2 が 1 回検出されたことを確認します。
次に、見つけたルールを頻度の順に入力テキストに適用します。ここでは、正規表現ライブラリなどを使用して一致を生成します。最も頻度の高いルールは、入力テキストと共通するトークンが最も多いため、一致する可能性が高くなります。
このアプローチの全体的な利点は、すべてのルールを入力に適用する必要はなく、入力と共通するトークンが少なくとも 1 つあるルールのみを適用する必要があることです。それらのルールの中でも、各ルールのトークン数の順に適用します。入力と共有し、一致するルールを見つけたら、おそらく残りの一致手順を中断できます (または、各ケースですべての一致ルールが必要かどうか、または非常に適切な一致の 1 つだけが必要かどうかによって異なります)。 )。
改善上記は、トークンに基づいてルールの事前選択を実行します。代わりに、次のようにすべてのルールを連結できます。
what is *||where is *||do * need to||...
次に、この連結された文字列のサフィックス配列を作成します。
次に、指定された入力文字列をサフィックス配列と照合して、1 つのトークンよりも小さい一致や複数のトークンにまたがる一致を含む、すべての部分文字列一致を識別します。*
上記の例では、ワイルドカード記号とがサフィックス配列に含まれていると想定してい$
ますが、もちろん、入力文字列のどの部分もそれらに一致することはありません。それらをサフィックス配列から除外するか、ダミー文字に置き換えることができます。
一致を判断したら、それらを長さで並べ替えます。また、連結された文字列内の一致位置をルール ID にマップする必要があります。これは、連結された文字列に対するルールの開始位置の配列を維持することによって容易に可能になります。インデックス付きビット ベクトルに基づく高度に最適化された方法もあります (必要に応じて詳しく説明します)。
一致するルール ID を取得したら、逆インデックスの場合と同じことを行います。標準の正規表現一致 (または類似のもの) を使用して、一致ルールを適用します。
繰り返しますが、このアプローチは比較的複雑であり、非常に大量のルールがあり、トークンベース (または部分文字列ベース) のルックアップによって候補ルールの数が大幅に削減される可能性がある場合にのみ意味があります。あなたが与えたルールの例から、この場合は後者を想定していますが、扱っているルールの数 (10k のオーダー) がこのアプローチを正当化するかどうかはわかりません。ルールの総数が 10 万または数百万の場合は、より理にかなっている可能性があります。