«1. Введение

В этом кратком руководстве мы узнаем, как обнаружить несколько слов внутри строки.

2. Наш пример

Предположим, у нас есть строка:

String inputString = "hello there, Baeldung";

Наша задача состоит в том, чтобы найти, содержит ли inputString слова «hello» и «Baeldung».

Итак, давайте сложим наши ключевые слова в массив:

String[] words = {"hello", "Baeldung"};

Более того, порядок слов не важен, и совпадения должны быть чувствительны к регистру.

3. Использование String.contains()

Для начала мы покажем, как использовать метод String.contains() для достижения нашей цели.

Давайте пройдемся по массиву ключевых слов и проверим наличие каждого элемента внутри inputString:

public static boolean containsWords(String inputString, String[] items) {
    boolean found = true;
    for (String item : items) {
        if (!inputString.contains(item)) {
            found = false;
            break;
        }
    }
    return found;
}

Метод contains() вернет true, если inputString содержит данный элемент. Когда у нас нет ни одного из ключевых слов внутри нашей строки, мы можем прекратить движение вперед и немедленно вернуть false.

Несмотря на то, что нам нужно написать больше кода, это решение работает быстро для простых случаев использования.

4. Использование String.indexOf()

Подобно решению, использующему метод String.contains(), мы можем проверить индексы ключевых слов с помощью метода String.indexOf(). Для этого нам нужен метод, принимающий inputString и список ключевых слов:

public static boolean containsWordsIndexOf(String inputString, String[] words) {
    boolean found = true;
    for (String word : words) {
        if (inputString.indexOf(word) == -1) {
            found = false;
            break;
        }
    }
    return found;
}

Метод indexOf() возвращает индекс слова внутри inputString. Когда у нас нет слова в тексте, индекс будет равен -1.

5. Использование регулярных выражений

Теперь давайте используем регулярное выражение для сопоставления наших слов. Для этого мы будем использовать класс Pattern.

Сначала определим строковое выражение. Поскольку нам нужно сопоставить два ключевых слова, мы построим правило регулярного выражения с двумя прогнозами:

Pattern pattern = Pattern.compile("(?=.*hello)(?=.*Baeldung)");

И для общего случая:

StringBuilder regexp = new StringBuilder();
for (String word : words) {
    regexp.append("(?=.*").append(word).append(")");
}

После этого мы будем использовать метод matcher() для find() вхождения:

public static boolean containsWordsPatternMatch(String inputString, String[] words) {

    StringBuilder regexp = new StringBuilder();
    for (String word : words) {
        regexp.append("(?=.*").append(word).append(")");
    }

    Pattern pattern = Pattern.compile(regexp.toString());

    return pattern.matcher(inputString).find();
}

Но регулярные выражения сказываются на производительности. Если у нас есть несколько слов для поиска, производительность этого решения может быть не оптимальной.

6. Использование Java 8 и List

И, наконец, мы можем использовать Stream API Java 8. Но сначала давайте сделаем небольшие преобразования с нашими исходными данными:

List<String> inputString = Arrays.asList(inputString.split(" "));
List<String> words = Arrays.asList(words);

Теперь пришло время использовать Stream API:

public static boolean containsWordsJava8(String inputString, String[] words) {
    List<String> inputStringList = Arrays.asList(inputString.split(" "));
    List<String> wordsList = Arrays.asList(words);

    return wordsList.stream().allMatch(inputStringList::contains);
}

Приведенный выше конвейер операций вернет true, если входная строка содержит все наши ключевые слова.

В качестве альтернативы мы можем просто использовать метод containsAll() фреймворка Collections для достижения желаемого результата:

public static boolean containsWordsArray(String inputString, String[] words) {
    List<String> inputStringList = Arrays.asList(inputString.split(" "));
    List<String> wordsList = Arrays.asList(words);

    return inputStringList.containsAll(wordsList);
}

Однако этот метод работает только для целых слов. Таким образом, он найдет наши ключевые слова, только если они разделены пробелами в тексте.

7. Использование алгоритма Ахо-Корасика

Проще говоря, алгоритм Ахо-Корасика предназначен для поиска текста по нескольким ключевым словам. Он имеет временную сложность O(n) независимо от того, сколько ключевых слов мы ищем или какова длина текста.

Давайте включим зависимость алгоритма Ахо-Корасика в наш pom.xml:

<dependency>
    <groupId>org.ahocorasick</groupId>
    <artifactId>ahocorasick</artifactId>
    <version>0.4.0</version>
</dependency>

Во-первых, давайте создадим тройной конвейер с массивом слов из ключевых слов. Для этого воспользуемся структурой данных Trie:

Trie trie = Trie.builder().onlyWholeWords().addKeywords(words).build();

После этого вызовем метод парсера с текстом inputString, в котором мы хотим найти ключевые слова, и сохраним результаты в коллекции emits: ~~ ~

Collection<Emit> emits = trie.parseText(inputString);

И, наконец, если мы напечатаем наши результаты:

emits.forEach(System.out::println);

Для каждого ключевого слова мы увидим начальную позицию ключевого слова в тексте, конечную позицию и само ключевое слово:

0:4=hello
13:20=Baeldung

~ ~~ Наконец, давайте посмотрим на полную реализацию:

public static boolean containsWordsAhoCorasick(String inputString, String[] words) {
    Trie trie = Trie.builder().onlyWholeWords().addKeywords(words).build();

    Collection<Emit> emits = trie.parseText(inputString);
    emits.forEach(System.out::println);

    boolean found = true;
    for(String word : words) {
        boolean contains = Arrays.toString(emits.toArray()).contains(word);
        if (!contains) {
            found = false;
            break;
        }
    }

    return found;
}

В этом примере мы ищем только целые слова. Итак, если мы хотим сопоставить не только inputString, но и «helloBaeldung», мы должны просто удалить атрибут onlyWholeWords() из конвейера построителя Trie.

Кроме того, имейте в виду, что мы также удаляем повторяющиеся элементы из коллекции emits, так как может быть несколько совпадений для одного и того же ключевого слова.

8. Заключение

В этой статье мы научились находить несколько ключевых слов внутри строки. Более того, мы показали примеры с использованием ядра JDK, а также с библиотекой Aho-Corasick.

Как обычно, полный код этой статьи доступен на GitHub.