文字列に(文字列の)リストの要素が含まれているかどうかを確認する


155

次のコードブロックの場合:

For I = 0 To listOfStrings.Count - 1
    If myString.Contains(lstOfStrings.Item(I)) Then
        Return True
    End If
Next
Return False

出力は次のとおりです。

ケース1:

myString: C:\Files\myfile.doc
listOfString: C:\Files\, C:\Files2\
Result: True

ケース2:

myString: C:\Files3\myfile.doc
listOfString: C:\Files\, C:\Files2\
Result: False

リスト(listOfStrings)には複数のアイテム(最小20)が含まれる場合があり、数千の文字列(myStringなど)に対してチェックする必要があります。

このコードを書くためのより良い(より効率的な)方法はありますか?

回答:


359

LINQを使用し、C#を使用します(最近のVBについてはあまり知りません)。

bool b = listOfStrings.Any(s=>myString.Contains(s));

または(短くて効率的ですが、間違いなく不明確です):

bool b = listOfStrings.Any(myString.Contains);

等価性をテストしている場合は、一見の価値HashSetがありますが、フラグメントに分割して複雑さの順序を追加しない限り、これは部分一致には役立ちません。


更新:本当に「StartsWith」を意味する場合は、リストを並べ替えて配列に配置できます。次に、Array.BinarySearch各項目を見つけるために使用します-完全一致か部分一致かをルックアップで確認します。


1
Containsの代わりに、彼の例に基づいてStartsWithを使用します。
tvanfosson

@tvanfosson-例が完全に包括的であるかどうかによって異なりますが、はい、同意します。もちろん変更は簡単です。
マークグラベル

このコードは、アルゴリズムレベルでどれほど効率的ですか?"Any"のループが高速であれば、短くて高速ですが、完全一致を何度も実行する必要があるという問題は同じです。
Torsten Marek、

セットを使用している場合は、カスタムコンパレータを設定できます。
Fortyrunner、2009

2番目の方法は、実際に測定可能な違いがあるため、それほど効率的ではありません。
ICR

7

あなたがあなたの文字列を構築するとき、それはこのようになるはず

bool inact = new string[] { "SUSPENDARE", "DIZOLVARE" }.Any(s=>stare.Contains(s));

5

以前の同様の質問「既存の文字列を比較可能な多数のリストに対してテストする最良の方法」からの提案がいくつかありました。

正規表現で十分かもしれません。式は、すべての候補部分文字列を連結したもので、|その間にOR " "演算子が含まれます。もちろん、式を作成するときにエスケープされていない文字に注意したり、複雑さやサイズの制限のためにコンパイルに失敗したりしないようにする必要があります。

これを行う別の方法は、すべての候補部分文字列を表すトライデータ構造を構築することです(これにより、正規表現マッチャーが行っていることと多少重複する場合があります)。テスト文字列の各文字をステップ実行するときに、トライのルートへの新しいポインターを作成し、既存のポインターを適切な子(存在する場合)に進めます。いずれかのポインタが葉に到達すると、一致が発生します。


5

私はマークの答えが好きでしたが、Cases InSenSiTiVeになるにはContainsマッチングが必要でした。

これが解決策でした:

bool b = listOfStrings.Any(s => myString.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0))

> -1にすべきではありませんか?
CSシャープ2016年

1
@CSharped> -1(マイナス1以上)と> = 0(ゼロ以上)は同じものであるため、重要ではありません。
WhoIsRich

2

パターンに基づく1つの改善は、ContainsではなくStartsWithを使用するように変更することです。StartsWithは、最初の不一致が見つかるまで各文字列を反復処理するだけでよく、文字位置が見つかったときにすべての文字位置で検索を再開する必要はありません。

また、パターンに基づいて、myStringのパスの最初の部分を抽出し、比較を逆にできるように見える可能性があります-逆ではなく、文字列のリストでmyStringの開始パスを探します。

string[] pathComponents = myString.Split( Path.DirectorySeparatorChar );
string startPath = pathComponents[0] + Path.DirectorySeparatorChar;

return listOfStrings.Contains( startPath );

EDITは:これは、あなたが変更される可能性があるため@Marc Gravellは言及HashSetのアイデア使用してさらに速くなりContainsにしますContainsKeyルックアップではなく、O(N)のO(1)になります。パスが正確に一致することを確認する必要があります。これは@Marc Gravellのように一般的な解決策ではありませんが、例に合わせて調整されていることに注意してください。

C#の例でごめんなさい。VBに変換するのに十分なコーヒーがありません。


再び始まる; おそらく事前にソートしてバイナリ検索を使用しますか?それは再び速いかもしれません。
Marc Gravell

2

古い質問。しかしVB.NET、当初の要件でした。受け入れられた回答と同じ値を使用する:

listOfStrings.Any(Function(s) myString.Contains(s))

1

速度をテストしましたか?

つまり、データのサンプルセットを作成してプロファイルを作成しましたか?思ったほど悪くないかもしれません。

これは、別のスレッドにスポーンして速度の錯覚を与えることもできるかもしれません!


0

速度が重要な場合は、パターンのセットに対してAho-Corasickアルゴリズムを探すことをお勧めします。

これは失敗リンクのあるトライです。つまり、複雑度はO(n + m + k)です。ここで、nは入力テキストの長さ、mはパターンの累積長、kは一致数です。最初の一致が見つかった後に終了するようにアルゴリズムを変更する必要があります。



0

Containsメソッドの欠点は、文字列を比較するときに重要になることが多い比較タイプを指定できないことです。常にカルチャと大文字と小文字が区別されます。WhoIsRichの答えは価値があると思うので、もっと簡単な代替案を示したいだけです。

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