1. Введение
В этом кратком руководстве мы узнаем, как обнаружить несколько слов внутри строки .
2. Наш пример
Предположим, у нас есть строка:
String inputString = "hello there, ForEach";
Наша задача — найти, содержит ли inputString слова
«hello»
и «ForEach»
.
Итак, давайте поместим наши ключевые слова в массив:
String[] words = {"hello", "ForEach"};
Кроме того, порядок слов не важен, и совпадения должны быть чувствительны к регистру.
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)(?=.*ForEach)");
И для общего случая:
StringBuilder regexp = new StringBuilder();
for (String word : words) {
regexp.append("(?=.*").append(word).append(")");
}
После этого мы будем использовать метод matcher()
для поиска
вхождений:
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 и списка
И, наконец, мы можем использовать 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 с массивом ключевых слов words .
Для этого мы будем использовать структуру данных 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=ForEach
Наконец, давайте посмотрим на полную реализацию:
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,
но и «helloForEach»
, мы должны просто удалить атрибут onlyWholeWords()
из конвейера построителя Trie .
Кроме того, имейте в виду, что мы также удаляем повторяющиеся элементы из коллекции emits
, поскольку для одного и того же ключевого слова может быть несколько совпадений.
8. Заключение
В этой статье мы узнали, как найти несколько ключевых слов внутри строки. Более того, мы показали примеры с использованием ядра JDK, а также с библиотекой Aho-Corasick
.
Как обычно, полный код для этой статьи доступен на GitHub .