シード文字列のリスト、約100の定義済み文字列があります。すべての文字列にはASCII文字のみが含まれます。
std::list<std::wstring> seeds{ L"google", L"yahoo", L"stackoverflow"};
私のアプリは、あらゆる文字を含むことができる多くの文字列を常に受け取ります。受信した各行を確認し、シードが含まれているかどうかを判断する必要があります。比較では大文字と小文字を区別する必要があります。
受信した文字列をテストするには、可能な限り高速なアルゴリズムが必要です。
現在、私のアプリはこのアルゴリズムを使用しています:
std::wstring testedStr;
for (auto & seed : seeds)
{
if (boost::icontains(testedStr, seed))
{
return true;
}
}
return false;
うまくいきますが、これが最も効率的な方法であるかどうかはわかりません。
より良いパフォーマンスを達成するために、アルゴリズムをどのように実装できますか?
これはWindowsアプリです。アプリは有効なstd::wstring
文字列。
更新
このタスクのために、Aho-Corasickアルゴを実装しました。誰かが私のコードをレビューすることができれば素晴らしいでしょう-私はそのようなアルゴリズムで大きな経験を持っていません。実装へのリンク: Gist.github.com
Aho–Corasickアルゴリズム を使用できます
それは、文字列がシードを持っていることを意味する終端としてマークされたいくつかの頂点でトライ/オートマトンを構築します。
O(sum of dictionary Word lengths)
に組み込まれており、O(test string length)
で答えを提供します
利点:
ASCII(non ASCII文字が一致しない場合)の場合、各シンボルを下げることで大文字と小文字を区別しないようにすることができます。
一致する文字列の数が限られている場合、これは、ルートからリーフまで読み取り、同様の文字列が同様のブランチを占めるようにツリーを構築できることを意味します。
これは trie、またはRadix Tree とも呼ばれます。
たとえば、文字列cat, coach, con, conch
と同様 dark, dad, dank, do
。彼らのトライは次のようになります。
ツリー内のいずれかの単語を検索すると、ルートからツリーが検索されます。リーフにすることは、シードに一致することに対応します。とにかく、文字列の各文字はその子の1つと一致する必要があります。そうでない場合は、検索を終了できます(たとえば、「g」で始まる単語や「cu」で始まる単語は考慮しません)。
ツリーを構築したり、検索したり、オンザフライで変更したりするためのさまざまなアルゴリズムがありますが、最適なアルゴリズムがわからないため、特定のソリューションではなくソリューションの概念的な概要を説明すると思いましたそれ。
概念的には、ツリーの検索に使用できるアルゴリズムは、文字列の文字が特定のカテゴリで取り得る固定量のカテゴリまたは値の radix sort の背後にあるアイデアに関連します。与えられた時点。
これにより、1つのWordを Word-list に対してチェックできます。このWordリストを入力文字列のサブ文字列として探しているので、これ以上のことがあります。
編集:他の回答でも述べたように、文字列マッチングのAho-Corasickアルゴリズムは、追加のリンクを持つトライから構成される文字列マッチングを実行するための高度なアルゴリズムです。ツリーを「ショートカット」し、これに伴う別の検索パターンを使用したため。 (興味深いメモとして、Alfred Ahoは、人気のあるコンパイラの教科書 Compilers:Principles、Techniques、and Tools およびアルゴリズムの教科書コンピューターアルゴリズムの設計と分析。彼はベル研究所の元メンバーでもあります。マーガレットJ.コラシックは、自身に関する公開情報があまりないようです。)
既存の正規表現ユーティリティを試す必要があります。手巻きアルゴリズムよりも遅いかもしれませんが、正規表現は複数の可能性を一致させることに関するものであるため、ハッシュマップまたはすべての文字列との単純な比較よりもすでに数倍高速です。正規表現の実装では、RiaDが言及したAho–Corasickアルゴリズムを既に使用している可能性があるため、基本的には十分にテストされた高速な実装を自由に使用できます。
C++ 11を使用している場合、標準の正規表現ライブラリが既にあります
#include <string>
#include <regex>
int main(){
std::regex self_regex("google|yahoo|stackoverflow");
regex_match(input_string ,self_regex);
}
これにより、可能な限り最適な最小一致ツリーが生成されると予想されるため、非常に高速(かつ信頼性の高い!)
より高速な方法の1つは、接尾辞ツリー https://en.wikipedia.org/wiki/Suffix_tree を使用することですが、このアプローチには大きな欠点があります-構築が難しく、データ構造が難しいです。このアルゴリズムにより、線形の複雑さで文字列からツリーを構築できます https://en.m.wikipedia.org/wiki/Ukkonen%27s_algorithm
編集:Matthieu M.が指摘したように、OPは文字列にキーワードが含まれているかどうかを尋ねました。私の答えは、文字列がキーワードと等しいか、文字列を分割できる場合にのみ機能しますスペース文字によって。
特に、可能性のある候補が多数あり、コンパイル時にgperfなどのツールで 完全なハッシュ関数 を使用してそれらを知ることは、試してみる価値があります。主な原則は、ジェネレーターにシードをシードし、すべてのシード値に対して衝突しないハッシュ関数を含む関数を生成することです。実行時に、関数に文字列を指定し、ハッシュを計算して、それがハッシュ値に対応する唯一の候補であるかどうかをチェックします。
実行時コストは、文字列をハッシュしてから、唯一の可能な候補(シードサイズの場合はO(1)、文字列の長さの場合はO(1)))と比較します。
比較の大文字と小文字を区別しないようにするには、シードと文字列でtolowerを使用します。
DarioOOの答えの変形として、文字列に Lexパーサー をコーディングすることで、正規表現の一致の実装を高速化することができます。通常は yacc と一緒に使用されますが、これはLexが単独でジョブを実行するケースであり、Lexパーサーは通常非常に効率的です。
Aho-Corasick 、 Commentz-Walter または Rabin-Karp などのアルゴリズムのように、すべての文字列が長い場合、このアプローチは失敗する可能性がありますおそらく大幅な改善を提供するでしょうし、Lex実装がそのようなアルゴリズムを使用しているとは思えません。
再構成せずに文字列を構成できるようにする必要がある場合、このアプローチは難しくなりますが、 flex はオープンソースであるため、コードを共食いすることができます。
文字列の数は大きくない(〜100)ため、次のアルゴリズムを使用できます。
int checks[N];
配列を作成します。std::map<int, std::vector<std::wstring>>
を作成できます。ここで、int
は文字列のチェックサムであり、ベクトルにはそのチェックサム。各長さ(最大N)でこのようなマップの配列を作成します。これはコンパイル時にも実行できます。checks
整数にX charの値を追加し、それぞれ(1からNまでの数字)の(XK)文字の値を削除する必要があります(Kは整数の数) checks
配列。したがって、checks
配列に格納されているすべての長さの正しいチェックサムが常に得られます。マップ上で検索した後、そのようなペア(長さとチェックサム)を持つ文字列が存在し、存在する場合は比較します。偽陽性の結果(チェックサムと長さが等しいがフレーズが異なる場合)は非常にまれです。
したがって、Rが大きな文字列の長さだとしましょう。それからそれをループするにはO(R)が必要です。各ステップでは、「+」の小さい数(char値)でN個の操作を実行し、「-」の小さい数(char値)でN個の操作を実行します。これは非常に高速です。各ステップでは、checks
配列でカウンターを検索する必要があります。これは1つのメモリブロックであるため、O(1)です。
また、各ステップは、マップの配列でマップを見つける必要があります。これは、1つのメモリブロックでもあるため、O(1)になります。また、マップ内では、log(F)の正しいチェックサムで文字列を検索する必要があります。ここで、Fはマップのサイズであり、通常は2〜3個の文字列を含むため、一般的にはO (1)。
また、チェックすることができ、同じチェックサムを持つ文字列がない場合(わずか100ワードで発生する可能性が高い)、マップの代わりにペアを格納して、マップをまったく破棄できます。
したがって、最終的には非常に小さなOでO(R)が得られます。このchecksum
の計算方法は変更できますが、非常に単純で完全に高速で、非常にまれな偽陽性反応があります。