Usando indexOf para localizar todas as ocorrências de uma palavra em uma seqüência de caracteres
1. Visão geral
A tarefa de procurar um padrão de caracteres ou uma palavra em uma sequência de texto maior é feita em vários campos. Em bioinformática, por exemplo, podemos precisar encontrar um trecho de DNA em um cromossomo.
Na mídia, os editores localizam uma frase específica em um texto volumoso. A vigilância de dados detecta fraudes ou spam procurando palavras suspeitas incorporadas nos dados.
Em qualquer contexto, a pesquisa é tão conhecida e assustadora que é popularmente chamada de "a agulha no problema do palheiro" *. Neste tutorial, demonstraremos um algoritmo simples que usa o https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#indexOf-java.lang.String-int- [indexOf (String str, int fromIndex)] method da classe Java String para localizar todas as ocorrências de uma palavra em uma string.
2. Algoritmo Simples
Em vez de simplesmente contar as ocorrências de uma palavra em um texto maior, nosso algoritmo encontrará e identificará todos os locais em que uma palavra específica existe no texto. Nossa abordagem para o problema é curta e simples, de modo que:
-
A pesquisa encontrará a palavra mesmo dentro das palavras no texto . Portanto, se estivermos pesquisando a palavra "capaz", encontraremos em "confortável" e "tablet".
-
A pesquisa não diferencia maiúsculas de minúsculas .
-
O algoritmo é baseado na abordagem de busca de cadeias ingênuas . Isso significa que, como somos ingênuos quanto à natureza dos caracteres da palavra e da sequência de texto, usaremos força bruta para verificar todos os locais do texto em busca de uma instância da palavra de pesquisa.
2.1. Implementação
Agora que definimos os parâmetros para nossa pesquisa, vamos escrever uma solução simples:
public class WordIndexer {
public List<Integer> findWord(String textString, String word) {
List<Integer> indexes = new ArrayList<Integer>();
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 Testando a solução
Para testar nosso algoritmo, usaremos um trecho de uma famosa passagem de Hamlet de Shakespeare e procuraremos a palavra "ou", que aparece cinco vezes:
@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<Integer> expectedResult = Arrays.asList(7, 122, 130, 221, 438);
List<Integer> actualResult = wordIndexer.findWord(theString, "or");
assertEquals(expectedResult, actualResult);
}
Quando executamos nosso teste, obtemos o resultado esperado. A pesquisa de "ou" produz cinco instâncias incorporadas de várias maneiras na cadeia de texto:
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"
Em termos matemáticos, o algoritmo possui uma notação Big-O de O (m * (n-m)) _, onde _m é o comprimento da palavra e n é o comprimento da cadeia de texto. Essa abordagem pode ser apropriada para seqüências de caracteres de palheiro de alguns milhares de caracteres, mas será intoleravelmente lenta se houver bilhões de caracteres.
3. Algoritmo aprimorado
O exemplo simples acima demonstra uma abordagem ingênua e de força bruta para procurar uma determinada palavra em uma sequência de texto. Como tal, funcionará para qualquer palavra de pesquisa e qualquer texto.
*Se soubermos antecipadamente que a palavra de pesquisa não contém um padrão repetitivo de caracteres, como "aaa", podemos escrever um algoritmo um pouco mais eficiente.*
Nesse caso, podemos evitar com segurança fazer o backup para verificar novamente todos os locais na cadeia de texto como um possível local inicial. Depois de fazer a chamada para o método _indexOf () _, simplesmente deslizaremos para o local logo após o final da última ocorrência encontrada. Esse ajuste simples gera um cenário de melhor caso de _O (n) _.
Vejamos esta versão aprimorada do método _findWord () _ anterior.
public List<Integer> findWordUpgrade(String textString, String word) {
List<Integer> indexes = new ArrayList<Integer>();
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. Conclusão
Neste tutorial, apresentamos um algoritmo de pesquisa que não diferencia maiúsculas de minúsculas para encontrar todas as variações de uma palavra em uma sequência de texto maior. Mas não deixe que isso oculte o fato de que o método Java String class '_indexOf () _ é inerentemente sensível a maiúsculas e minúsculas e pode distinguir entre "Bob" e "bob", por exemplo.
No total, _indexOf () _ é um método conveniente para localizar uma sequência de caracteres oculta em uma sequência de texto sem codificar as manipulações de substring.
Como de costume, a base de código completa deste exemplo é over no GitHub.