web-dev-qa-db-ja.com

Java Regex、文字列にセット内の単語のいずれかが含まれているかどうかを確認する方法?

アップル、オレンジ、ナシ、バナナ、キウイなどの言葉があります

文に上記の単語のいずれかが含まれているかどうかを確認し、含まれている場合は、どの単語が一致したかを調べます。 Regexでこれを達成するにはどうすればよいですか?

現在、各単語セットに対してString.indexOf()を呼び出しています。私はこれが正規表現のマッチングほど効率的ではないと仮定していますか?

35
user193116

TL; DR単純な部分文字列の場合はcontains()が最適ですが、単語全体の一致の場合は正規表現の方がおそらく優れています。

どのメソッドがより効率的かを確認する最良の方法は、テストすることです。

String.contains()の代わりにString.indexOf()を使用して、正規表現以外のコードを簡素化できます。

別の単語を検索するには、正規表現は次のようになります。

_Apple|orange|pear|banana|kiwi
_

_|_は、正規表現でORとして機能します。

私の非常に単純なテストコードは次のようになります。

_public class TestContains {

   private static String containsWord(Set<String> words,String sentence) {
     for (String Word : words) {
       if (sentence.contains(Word)) {
         return Word;
       }
     }

     return null;
   }

   private static String matchesPattern(Pattern p,String sentence) {
     Matcher m = p.matcher(sentence);

     if (m.find()) {
       return m.group();
     }

     return null;
   }

   public static void main(String[] args) {
     Set<String> words = new HashSet<String>();
     words.add("Apple");
     words.add("orange");
     words.add("pear");
     words.add("banana");
     words.add("kiwi");

     Pattern p = Pattern.compile("Apple|orange|pear|banana|kiwi");

     String noMatch = "The quick brown fox jumps over the lazy dog.";
     String startMatch = "An Apple is Nice";
     String endMatch = "This is a longer sentence with the match for our fruit at the end: kiwi";

     long start = System.currentTimeMillis();
     int iterations = 10000000;

     for (int i = 0; i < iterations; i++) {
       containsWord(words, noMatch);
       containsWord(words, startMatch);
       containsWord(words, endMatch);
     }

     System.out.println("Contains took " + (System.currentTimeMillis() - start) + "ms");
     start = System.currentTimeMillis();

     for (int i = 0; i < iterations; i++) {
       matchesPattern(p,noMatch);
       matchesPattern(p,startMatch);
       matchesPattern(p,endMatch);
     }

     System.out.println("Regular Expression took " + (System.currentTimeMillis() - start) + "ms");
   }
}
_

私が得た結果は次のとおりです。

_Contains took 5962ms
Regular Expression took 63475ms
_

明らかに、タイミングは、検索対象の単語の数と検索対象の文字列によって異なりますが、contains()は、このような単純な検索の正規表現よりも約10倍速いようです。

正規表現を使用して別の文字列内の文字列を検索することにより、ハンマーを使用してナットをクラックしているので、それが遅いことに驚かないでください。検索するパターンがより複雑な場合に備えて、正規表現を保存します。

正規表現を使用したい場合の1つは、indexOf()およびcontains()がジョブを実行しない場合です部分文字列だけでなく、たとえばpearと一致しますが、spearsとは一致しません。正規表現は 単語境界 の概念を持っているため、このケースをうまく処理します。

この場合、パターンを次のように変更します。

_\b(Apple|orange|pear|banana|kiwi)\b
_

_\b_は、Wordの先頭または末尾にのみ一致することを示し、括弧はOR式をグループ化します。

コードでこのパターンを定義する場合、別のバックスラッシュでバックスラッシュをエスケープする必要があることに注意してください。

_ Pattern p = Pattern.compile("\\b(Apple|orange|pear|banana|kiwi)\\b");
_
48
Dave Webb

正規表現がパフォーマンスの点でより良い仕事をするとは思いませんが、次のように使用できます:

Pattern p = Pattern.compile("(Apple|orange|pear)");
Matcher m = p.matcher(inputString);
while (m.find()) {
   String matched = m.group(1);
   // Do something
}
7
Guillaume Polet

これが私が見つけた最も簡単な解決策です(ワイルドカードと一致):

boolean a = str.matches(".*\\b(wordA|wordB|wordC|wordD|wordE)\\b.*");
4
Yanir Calisar