web-dev-qa-db-ja.com

事前定義されたシードのリストに対する文字列テスト用の最速のC ++アルゴリズム(大文字と小文字は区別されません)

シード文字列のリスト、約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

53
Victor Mezrin

Aho–Corasickアルゴリズム を使用できます

それは、文字列がシードを持っていることを意味する終端としてマークされたいくつかの頂点でトライ/オートマトンを構築します。

O(sum of dictionary Word lengths)に組み込まれており、O(test string length)で答えを提供します

利点:

  • 特に複数の辞書の単語で動作し、チェック時間は単語の数に依存しません(メモリに収まらない場合などを考慮しない場合)
  • アルゴリズムの実装は難しくありません(少なくとも接尾辞構造と比較して)

ASCII(non ASCII文字が一致しない場合)の場合、各シンボルを下げることで大文字と小文字を区別しないようにすることができます。

50
RiaD

一致する文字列の数が限られている場合、これは、ルートからリーフまで読み取り、同様の文字列が同様のブランチを占めるようにツリーを構築できることを意味します。

これは trie、またはRadix Tree とも呼ばれます。

たとえば、文字列cat, coach, con, conch と同様 dark, dad, dank, do。彼らのトライは次のようになります。

enter image description here

ツリー内のいずれかの単語を検索すると、ルートからツリーが検索されます。リーフにすることは、シードに一致することに対応します。とにかく、文字列の各文字はその子の1つと一致する必要があります。そうでない場合は、検索を終了できます(たとえば、「g」で始まる単語や「cu」で始まる単語は考慮しません)。

ツリーを構築したり、検索したり、オンザフライで変更したりするためのさまざまなアルゴリズムがありますが、最適なアルゴリズムがわからないため、特定のソリューションではなくソリューションの概念的な概要を説明すると思いましたそれ。

概念的には、ツリーの検索に使用できるアルゴリズムは、文字列の文字が特定のカテゴリで取り得る固定量のカテゴリまたは値の radix sort の背後にあるアイデアに関連します。与えられた時点。

これにより、1つのWordを Word-list に対してチェックできます。このWordリストを入力文字列のサブ文字列として探しているので、これ以上のことがあります。

編集:他の回答でも述べたように、文字列マッチングのAho-Corasickアルゴリズムは、追加のリンクを持つトライから構成される文字列マッチングを実行するための高度なアルゴリズムです。ツリーを「ショートカット」し、これに伴う別の検索パターンを使用したため。 (興味深いメモとして、Alfred Ahoは、人気のあるコンパイラの教科書 Compilers:Principles、Techniques、and Tools およびアルゴリズムの教科書コンピューターアルゴリズムの設計と分析。彼はベル研究所の元メンバーでもあります。マーガレットJ.コラシックは、自身に関する公開情報があまりないようです。)

56
VermillionAzure

既存の正規表現ユーティリティを試す必要があります。手巻きアルゴリズムよりも遅いかもしれませんが、正規表現は複数の可能性を一致させることに関するものであるため、ハッシュマップまたはすべての文字列との単純な比較よりもすでに数倍高速です。正規表現の実装では、RiaDが言及したAho–Corasickアルゴリズムを既に使用している可能性があるため、基本的には十分にテストされた高速な実装を自由に使用できます。

C++ 11を使用している場合、標準の正規表現ライブラリが既にあります

#include <string>
#include <regex>

int main(){
     std::regex self_regex("google|yahoo|stackoverflow");
     regex_match(input_string ,self_regex);
}

これにより、可能な限り最適な最小一致ツリーが生成されると予想されるため、非常に高速(かつ信頼性の高い!)

16
GameDeveloper

より高速な方法の1つは、接尾辞ツリー https://en.wikipedia.org/wiki/Suffix_tree を使用することですが、このアプローチには大きな欠点があります-構築が難しく、データ構造が難しいです。このアルゴリズムにより、線形の複雑さで文字列からツリーを構築できます https://en.m.wikipedia.org/wiki/Ukkonen%27s_algorithm

10
LmTinyToon

編集:Matthieu M.が指摘したように、OPは文字列にキーワードが含まれているかどうかを尋ねました。私の答えは、文字列がキーワードと等しいか、文字列を分割できる場合にのみ機能しますスペース文字によって。

特に、可能性のある候補が多数あり、コンパイル時にgperfなどのツールで 完全なハッシュ関数 を使用してそれらを知ることは、試してみる価値があります。主な原則は、ジェネレーターにシードをシードし、すべてのシード値に対して衝突しないハッシュ関数を含む関数を生成することです。実行時に、関数に文字列を指定し、ハッシュを計算して、それがハッシュ値に対応する唯一の候補であるかどうかをチェックします。

実行時コストは、文字列をハッシュしてから、唯一の可能な候補(シードサイズの場合はO(1)、文字列の長さの場合はO(1)))と比較します。

比較の大文字と小文字を区別しないようにするには、シードと文字列でtolowerを使用します。

6
H. Idden

DarioOOの答えの変形として、文字列に Lexパーサー をコーディングすることで、正規表現の一致の実装を高速化することができます。通常は yacc と一緒に使用されますが、これはLexが単独でジョブを実行するケースであり、Lexパーサーは通常非常に効率的です。

Aho-CorasickCommentz-Walter または Rabin-Karp などのアルゴリズムのように、すべての文字列が長い場合、このアプローチは失敗する可能性がありますおそらく大幅な改善を提供するでしょうし、Lex実装がそのようなアルゴリズムを使用しているとは思えません。

再構成せずに文字列を構成できるようにする必要がある場合、このアプローチは難しくなりますが、 flex はオープンソースであるため、コードを共食いすることができます。

3
PJTraill

文字列の数は大きくない(〜100)ため、次のアルゴリズムを使用できます。

  1. Wordの最大長を計算します。 Nにします。
  2. チェックサムのint checks[N];配列を作成します。
  3. チェックサムは、検索フレーズのすべての文字の合計になります。そのため、リスト(コンパイル時に既知)から各Wordのこのようなチェックサムを計算し、std::map<int, std::vector<std::wstring>>を作成できます。ここで、intは文字列のチェックサムであり、ベクトルにはそのチェックサム。各長さ(最大N)でこのようなマップの配列を作成します。これはコンパイル時にも実行できます。
  4. 次に、大きな文字列をポインタで移動します。ポインターがX文字を指す場合、すべての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の計算方法は変更できますが、非常に単純で完全に高速で、非常にまれな偽陽性反応があります。

3
Arkady