3


1

多くの入力を持つC#の効率的な部分文字列

私が外部ライブラリや1ダース以上の余分なコード行を使用したくないとします。 * _ * _ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *)

明らかに `objString.Contains(objString2)`を使って単純なサブストリングチェックをすることができます。 しかし、特に複数の文字列を扱う場合には、特殊な状況下でこれよりも優れたパフォーマンスを発揮できる、よく知られたアルゴリズムが数多くあります。 しかし、そのようなアルゴリズムを私のコードに組み込むと、おそらく長さと複雑さが増すことになるので、組み込み関数に基づく何らかのショートカットを使用したいと思います。

E.g. 入力は、文字列の集まり、除外キーワードの集まり、除外キーワードの集まりです。 出力は、最初のキーワードのコレクションのサブセットになります。すべてのキーワードに、1つ以上の正のキーワードと0つの負のキーワードが含まれています。

おお、そして提案された解決策として正規表現を言及しないでください。

私の要求は相互に排他的であるということかもしれません(それほど多くの余分なコードも、外部ライブラリも正規表現もない、String.Containsより良いです)、しかし私は私が尋ねたいと思いました。

編集する

多くの人が、賢明な使い方をしたにもかかわらず含まれているにも関わらず、愚かな改良を提供しているだけです。 何人かの人々はContainsをもっと知的に呼び出そうとしています、それは私の質問のポイントを完全に見逃しています。 だからここに解決しようとする問題の例です。 LBushkinのソリューションは、誰かが標準よりも漸近的に優れているソリューションを提供する例です。

5〜15文字の長さの正のキーワードが10,000個、除外キーワードが0個(これは人を混乱させるようです)、および文字列が1,100万文字であるとします。 1,000,000文字の文字列に少なくとも1つの除外キーワードが含まれているかどうかを確認してください。

私は一つの解決策はFSAを作成することだと思います。 もう1つは、スペースの区切りとハッシュの使用です。

7 Answer


2


「否定的および肯定的」なキーワードについてのあなたの議論は多少混乱します - そしてより完全な答えを得るためにいくらかの明確化を使用することができます。

すべてのパフォーマンス関連の質問と同様に、まず単純なバージョンを作成し、次にそれをプロファイルしてボトルネックがどこにあるかを判断する必要があります。 そうは言っても…​

検索を最適化する1つの方法は(スペースを含む可能性があるフレーズではなく「単語」を常に検索している場合)、文字列から検索インデックスを作成することです。

検索インデックスは、ソートされた配列(バイナリ検索用)または辞書のいずれかです。 辞書はO(1)ルックアップの内部的なハッシュマップであるため、辞書はより早く証明される可能性があります。また辞書は検索ソース内の重複する値を自然に排除するため、実行する必要がある比較の数が減ります。

一般的な検索アルゴリズムは次のとおりです。

あなたが探しているそれぞれの文字列に対して:

  • 検索対象の文字列を取得し、トークン化する 個々の単語(空白で区切られます)

  • トークンを検索インデックス(ソートされた配列または 辞書)

  • 「除外キーワード」のインデックスを検索し、見つかった場合はスキップします 次の検索文字列へ

  • 「ポジティブキーワード」のインデックスを検索し、見つかったら追加します それらを辞書に追加します(単語が出現する頻度のカウントも追跡できます)

これはC#2.0でソートされた配列とバイナリ検索を使った例です:

_注意:あなたは十分に簡単に `string []`から `List`に切り替えることができます。私はそれをあなたに任せます。

string[] FindKeyWordOccurence( string[] stringsToSearch,
                               string[] positiveKeywords,
                               string[] negativeKeywords )
{
   Dictionary foundKeywords = new Dictionary();
   foreach( string searchIn in stringsToSearch )
   {
       // tokenize and sort the input to make searches faster
       string[] tokenizedList = searchIn.Split( ' ' );
       Array.Sort( tokenizedList );

       // if any negative keywords exist, skip to the next search string...
       foreach( string negKeyword in negativeKeywords )
           if( Array.BinarySearch( tokenizedList, negKeyword ) >= 0 )
               continue; // skip to next search string...

       // for each positive keyword, add to dictionary to keep track of it
       // we could have also used a SortedList, but the dictionary is easier
       foreach( string posKeyword in positiveKeyWords )
           if( Array.BinarySearch( tokenizedList, posKeyword ) >= 0 )
               foundKeywords[posKeyword] = 1;
   }

   // convert the Keys in the dictionary (our found keywords) to an array...
   string[] foundKeywordsArray = new string[foundKeywords.Keys.Count];
   foundKeywords.Keys.CopyTo( foundKeywordArray, 0 );
   return foundKeywordsArray;
}

これは、C#3.0で辞書ベースのインデックスとLINQを使用するバージョンです。

_注意:これは最もLINQ的なやり方ではありません。Union()とSelectMany()を使用して、アルゴリズム全体を1つの大きなLINQステートメントとして記述することができます。

public IEnumerable FindOccurences( IEnumerable searchStrings,
                                           IEnumerable positiveKeywords,
                                           IEnumerable negativeKeywords )
    {
        var foundKeywordsDict = new Dictionary();
        foreach( var searchIn in searchStrings )
        {
            // tokenize the search string...
            var tokenizedDictionary = searchIn.Split( ' ' ).ToDictionary( x => x );
            // skip if any negative keywords exist...
            if( negativeKeywords.Any( tokenizedDictionary.ContainsKey ) )
                continue;
            // merge found positive keywords into dictionary...
            // an example of where Enumerable.ForEach() would be nice...
            var found = positiveKeywords.Where(tokenizedDictionary.ContainsKey)
            foreach (var keyword in found)
                foundKeywordsDict[keyword] = 1;
        }
        return foundKeywordsDict.Keys;
    }


1


この拡張方法を追加すると、

public static bool ContainsAny(この文字列はtestString、IEnumerableキーワード) falseを返します。 }

それからこれは1行のステートメントになります。

var results = testStrings.Where(t =>!t.ContainsAny(badKeywordCollection))。ここで、(t => t.ContainsAny(goodKeywordCollection));

LINQの結果のストリーミングが不要な包含呼び出しを妨げるので、これは必ずしも包含チェックを行うよりも速くはありません。 加えて、結果として得られるコードが1つのライナーであることは素晴らしいことです。


1


本当にスペース区切りの単語を探しているのであれば、このコードは非常に単純な実装になります。

"; static void Main(string [] args){string sIn ="これは文字列であるべき長さではありませんが、それでもアルゴリズムを証明するのに役立ちます。 string [] sFor = {"string"、 "as"、 "not"}; Console.WriteLine(string.Join( "、"、FindAny(sIn、sFor))); }

プライベート静的文字列[] FindAny(文字列searchIn、文字列[] searchFor){HashSet hsIn = new HashSet(searchIn.Split());} HashSet hsFor = new HashSet(searchFor); hsIn.Intersect(hsFor).ToArray()を返します。 }

はい/いいえの答えだけが欲しい場合は(今のところそうなっているかもしれませんが)ハッシュセット "Overlaps"の別の方法があります。

プライベート静的ブール値FindAny(string searchIn、string [] searchFor){HashSet hsIn = new HashSet(searchIn.Split());} HashSet hsFor = new HashSet(searchFor); hsIn.Overlaps(hsFor)を返します。 }


0


まあ、あなたが文字列で呼び出すことができるSplit()メソッドがあります。 Split()を使用して入力文字列を単語の配列に分割してから、キーワードを含む単語を1対1でチェックできます。 ただし、これがContains()を使用するよりも速い場合があるかどうか、またはどのような状況下ではわかりません。


0


まず、否定的な単語を含む文字列をすべて取り除きます。 Containsメソッドを使用してこれを行うことをお勧めします。 Contains()は分割、ソート、検索よりも速いと思います。


0


これを行う最良の方法はあなたのマッチ文字列(正と負の両方)を取り、それらのハッシュを計算することであるように私には思えます。 次に、100万個の文字列を計算するn個のハッシュ(長さ5〜15の文字列の場合は10個)を調べて、一致文字列のハッシュと照合します。 あなたがハッシュマッチを得るならば、あなたは誤検知を除外するために実際の文字列比較をします。 マッチ文字列を長さ順にまとめ、特定のバケットの文字列サイズに基づいてハッシュを作成することで、これを最適化するための優れた方法がいくつかあります。

だから、あなたは何かのようになる:

IListバケット= BuildBuckets(matchStrings); int shortestLength = buckets [0] .Length; (int i = 0; i <inputString.Length  -  shortestLength; i){foreach(バケット内のバケットb){(if(i b.Length> = inputString.Length);文字列候補= inputString.Substring(i、b。長さ); int hash = ComputeHash(候補);

foreach(MatchStringのb.MatchStringsとの一致){if(hash!= match.Hash)は続行します。 if(候補== match.String){if(match.IsPositive){//肯定ケース} else {//否定ケース}}}}}


0


Contains()を最適化するためには、ポジティブ/ネガティブな単語のツリー(またはトライ)構造が必要です。

それはすべて(O(n)vs O(nm)、n =文字列のサイズ、m =平均ワードサイズ)をスピードアップし、コードは比較的小さいです