2012-03-01 15 views
32

Mam zestaw słów powiedzieć - jabłko, pomarańcza, gruszka, banan, kiwiKorzystanie z Java Regex, jak sprawdzić, czy ciąg zawiera dowolne ze słów w zestawie?

Chcę sprawdzić, czy zdanie zawiera którekolwiek z wyżej wymienionych słów, a jeśli tak, chcę znaleźć które słowo dopasowane. Jak mogę to osiągnąć w Regex?

Obecnie wywołuję String.indexOf() dla każdego z mojego zestawu słów. Zakładam, że nie jest to tak skuteczne, jak dopasowanie do wyrażenia regularnego?

Odpowiedz

47

TL; DR W przypadku prostych podciągi jest najlepszy, ale dla tylko dopasowanie całych słów Wyrażenie regularne jest prawdopodobnie lepsze.

Najlepszym sposobem sprawdzenia, która metoda jest bardziej wydajna, jest jej przetestowanie.

Możesz użyć String.contains() zamiast String.indexOf(), aby uprościć swój kod nieregexp.

Aby szukać różnych słów wyrażenie regularne wygląda następująco:

apple|orange|pear|banana|kiwi 

W | pracuje jako OR w wyrażeniach regularnych.

My bardzo prosty kod testowy wygląda następująco:

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"); 
    } 
} 

Wyniki dostałem były następujące:

Contains took 5962ms 
Regular Expression took 63475ms 

Oczywiście czasy będą się różnić w zależności od liczby słów poszukiwany i Przeszukiwane ciągi znaków, ale wydaje się być ~ 10 razy szybsze od wyrażeń regularnych dla prostego wyszukiwania, takiego jak to.

Używając Wyrażeń regularnych do wyszukiwania Ciągów w innym Łańcuchu używasz młota do złamania nakrętki, więc myślę, że nie powinniśmy być zaskoczeni, że jest wolniej. Zapisz wyrazy regularne, gdy wzory, które chcesz znaleźć, są bardziej złożone.

jeden przypadek, w którym możesz użyć wyrażeń regularnych jest jeśli indexOf() i nie będzie wykonać zadanie, ponieważ chcesz tylko dopasować całe słowa a nie tylko podciągi, na przykład chcesz dopasować pear, ale nie spears. Wyrażenia regularne obsługują ten przypadek, ponieważ mają one koncepcję word boundaries.

W tym przypadku chcemy zmienić nasz wzór do:

\b(apple|orange|pear|banana|kiwi)\b 

\b mówi tylko dopasować początek lub koniec słowa i grupę wsporniki razem lub wyrażenia.

Uwaga, przy definiowaniu tego wzoru w kodzie trzeba uciec backslashy z innym backslashem:

Pattern p = Pattern.compile("\\b(apple|orange|pear|banana|kiwi)\\b"); 
7

Nie sądzę wyrażeniem regularnym zrobi się lepiej pod względem wydajności, ale można go używać w sposób następujący:

Pattern p = Pattern.compile("(apple|orange|pear)"); 
Matcher m = p.matcher(inputString); 
while (m.find()) { 
    String matched = m.group(1); 
    // Do something 
} 
+5

Nie możesz po prostu przeczytać? Nigdy nie powiedziałem, że to było wydajne. –

+1

Wydajność zależy od długości regex. Jeśli ma mniej niż 1000 znaków, przejdź do niego. Jeśli będzie dłuższy, potrzebujesz innego rozwiązania. Na przykład podziel tekst na osobne wyrazy i sprawdź je względem wcześniej zdefiniowanej tabeli mieszania/zestawu "znanych" słów. – AlexR

+2

@deporter celem odpowiedzi jest wskazanie, jak rozwiązać pytanie, aby nie zapewnić doskonałego, lśniącego, światowej klasy rozwiązania. Można go łatwo poprawić, a jeśli chodzi o czytelność, jeśli masz 200 ciągów znaków (jeszcze jeden powód, aby nie używać do tego wyrażeń regularnych), możesz użyć pętli for i połączyć się w 'StringBuilder'. Myślę, że moja odpowiedź zapewnia wystarczająco dużo smaku. –

2

Oto najprostsze rozwiązanie znalazłem (dopasowanie z symboli wieloznacznych):

boolean a = str.matches(".*\\b(wordA|wordB|wordC|wordD|wordE)\\b.*"); 
Powiązane problemy