Использование indexOf для поиска всех вхождений слова в строке
1. обзор
Работа по поиску комбинации символов или слова в текстовой строке большего размера выполняется в различных полях. Например, в биоинформатике нам может понадобиться найти фрагмент ДНК в хромосоме.
В средствах массовой информации редакторы находят определенную фразу в объемном тексте. Наблюдение за данными обнаруживает мошенничество или спам путем поиска подозрительных слов, встроенных в данные.
В любом контексте поиск настолько известен и представляет собой сложную задачу, что в народе его называютthe “Needle in a Haystack Problem”. В этом руководстве мы продемонстрируем простой алгоритм, использующий методindexOf(String str, int fromIndex) классаString Java для поиска всех вхождений слова в строке.
2. Простой алгоритм
Вместо простого подсчета вхождений слова в тексте большего размера, наш алгоритм найдет и идентифицирует каждое место, где в тексте существует определенное слово. Наш подход к проблеме является коротким и простым, чтобы:
-
Поискwill find the word even within words in the text. Следовательно, если мы ищем слово «способный», мы найдем его в словах «удобный» и «планшет».
-
Поискwill be case-insensitive.
-
Алгоритмis based on the naïve string search approach. Это означает, что, поскольку мы наивны относительно характера символов в слове и текстовой строке, мы будем использовать грубую силу, чтобы проверить каждое место текста на наличие вхождения искомого слова.
2.1. Реализация
Теперь, когда мы определили параметры поиска, давайте напишем простое решение:
public class WordIndexer {
public List findWord(String textString, String word) {
List indexes = new ArrayList();
String lowerCaseTextString = textString.toLowerCase();
String lowerCaseWord = word.toLowerCase();
int index = 0;
while(index != -1){
index = lowerCaseTextString.indexOf(lowerCaseWord, index);
if (index != -1) {
indexes.add(index);
index++;
}
}
return indexes;
}
}
2.2. Тестирование решения
Чтобы протестировать наш алгоритм, мы воспользуемся фрагментом известного отрывка из Шекспировского Гамлета и введем в поиск слово «или», которое встречается пять раз:
@Test
public void givenWord_whenSearching_thenFindAllIndexedLocations() {
String theString;
WordIndexer wordIndexer = new WordIndexer();
theString = "To be, or not to be: that is the question: "
+ "Whether 'tis nobler in the mind to suffer "
+ "The slings and arrows of outrageous fortune, "
+ "Or to take arms against a sea of troubles, "
+ "And by opposing end them? To die: to sleep; "
+ "No more; and by a sleep to say we end "
+ "The heart-ache and the thousand natural shocks "
+ "That flesh is heir to, 'tis a consummation "
+ "Devoutly to be wish'd. To die, to sleep; "
+ "To sleep: perchance to dream: ay, there's the rub: "
+ "For in that sleep of death what dreams may come,";
List expectedResult = Arrays.asList(7, 122, 130, 221, 438);
List actualResult = wordIndexer.findWord(theString, "or");
assertEquals(expectedResult, actualResult);
}
Когда мы запускаем наш тест, мы получаем ожидаемый результат. Searching for “or” yields five instances embedded in various ways in the text string:с
index of 7, in "or"
index of 122, in "fortune"
index of 130, in "Or
index of 221, in "more"
index of 438, in "For"
С математической точки зрения алгоритм имеет нотацию Big-OO(m*(n-m)), гдеm - длина слова, аn - длина текстовой строки. Этот подход может быть подходящим для текстовых строк из стога сена в несколько тысяч символов, но будет невыносимо медленным, если есть миллиарды символов.
3. Улучшенный алгоритм
Простой пример, приведенный выше, демонстрирует наивный грубый подход к поиску данного слова в текстовой строке. Таким образом, он будет работать для любого поискового слова и любого текста.
Если мы заранее знаем, что искомое слово не содержит повторяющихся символов, таких как «ааа», то мы можем написать немного более эффективный алгоритм.
В этом случае мы можем безопасно избежать резервного копирования, чтобы перепроверить каждое место в текстовой строке как потенциальное начальное местоположение. После того, как мы вызовем методindexOf( ), мы просто переместимся в место сразу после конца последнего найденного вхождения. Эта простая настройка дает наилучший сценарийO(n).
Давайте посмотрим на эту расширенную версию более раннего методаfindWord( ).
public List findWordUpgrade(String textString, String word) {
List indexes = new ArrayList();
StringBuilder output = new StringBuilder();
String lowerCaseTextString = textString.toLowerCase();
String lowerCaseWord = word.toLowerCase();
int wordLength = 0;
int index = 0;
while(index != -1){
index = lowerCaseTextString.indexOf(lowerCaseWord, index + wordLength); // Slight improvement
if (index != -1) {
indexes.add(index);
}
wordLength = word.length();
}
return indexes;
}
4. Заключение
В этом уроке мы представили алгоритм поиска без учета регистра, чтобы найти все варианты слова в большей текстовой строке. Но не позволяйте этому скрывать тот факт, что методindexOf() класса JavaString по своей природе чувствителен к регистру и может различать, например, «Bob» и «bob».
В целомindexOf() представляет собой удобный метод поиска последовательности символов, скрытой в текстовой строке, без какого-либо кодирования для манипуляций с подстрокой.
Как обычно, полная кодовая база этого примера -over on GitHub.