Использование indexOf для поиска всех вхождений слова в строке

Использование indexOf для поиска всех вхождений слова в строке

1. обзор

Работа по поиску комбинации символов или слова в текстовой строке большего размера выполняется в различных полях. Например, в биоинформатике нам может понадобиться найти фрагмент ДНК в хромосоме.

В средствах массовой информации редакторы находят определенную фразу в объемном тексте. Наблюдение за данными обнаруживает мошенничество или спам путем поиска подозрительных слов, встроенных в данные.

В любом контексте поиск настолько известен и представляет собой сложную задачу, что в народе его называютthe “Needle in a Haystack Problem”. В этом руководстве мы продемонстрируем простой алгоритм, использующий методindexOf(String str, int fromIndex)  классаString Java для поиска всех вхождений слова в строке.

2. Простой алгоритм

Вместо простого подсчета вхождений слова в тексте большего размера, наш алгоритм найдет и идентифицирует каждое место, где в тексте существует определенное слово. Наш подход к проблеме является коротким и простым, чтобы:

  1. Поискwill find the word even within words in the text. Следовательно, если мы ищем слово «способный», мы найдем его в словах «удобный» и «планшет».

  2. Поискwill be case-insensitive.

  3. Алгоритм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.