Introdução
Contar o número de ocorrências de palavras em uma string é uma tarefa bastante fácil, mas tem várias abordagens para isso. Você também deve levar em conta a eficiência do método, já que normalmente você deseja empregar ferramentas automatizadas quando não deseja realizar trabalho manual – ou seja, quando o espaço de pesquisa é grande.
Neste guia, você aprenderá a contar o número de ocorrências de palavras em uma string em Java:
String searchText = "Your body may be chrome, but the heart never changes. It wants what it wants.";
String targetWord = "wants";
Procuraremos o número de ocorrências do targetWord
, Utilizando String.split()
, Collections.frequency()
e Expressões Regulares.
Contar ocorrências de palavras em string com String.split ()
A maneira mais simples de contar a ocorrência de uma palavra alvo em uma string é dividir a string em cada palavra e iterar pelo array, incrementando um wordCount
em cada partida. Observe que quando uma palavra tem algum tipo de pontuação ao seu redor, como wants.
no final da frase – a divisão simples no nível da palavra tratará corretamente wants
e wants.
como palavras separadas!
Para contornar isso, você pode remover facilmente toda a pontuação da frase antes dividindo-o:
String[] words = searchText.replaceAll("p{Punct}", "").split(" ");
int wordCount = 0;
for (int i=0; i < words.length; i++)
if (words[i].equals(targetWord))
wordCount++;
System.out.println(wordCount);
No for
loop, simplesmente iteramos pelo array, verificando se o elemento em cada índice é igual ao targetWord
. Se for, incrementamos o wordCount
, que ao final da execução imprime:
2
Contar ocorrências de palavras em string com Coleções.frequência()
A Collections.frequency()
fornece uma implementação muito mais limpa e de alto nível, que abstrai uma simples for
loop e verifica a identidade (se um objeto is outro objeto) e igualdade (se um objeto é igual a outro objeto, dependendo das características qualitativas desse objeto).
A frequency()
O método aceita uma lista para pesquisar e o objeto de destino e também funciona para todos os outros objetos, onde o comportamento depende de como o próprio objeto implementa equals()
. No caso das cordas, equals()
verifica para o conteúdo da string:
searchText = searchText.replaceAll("p{Punct}", "");
int wordCount = Collections.frequency(Arrays.asList(searchText.split(" ")), targetWord);
System.out.println(wordCount);
Aqui, convertemos o array obtido de split()
em um Java ArrayList
, usando o auxiliar asList()
método do Arrays
classe. A operação de redução frequency()
retorna um inteiro denotando a frequência de targetWord
na lista e resulta em:
2
Ocorrências de palavras em String com Matcher (Expressões Regulares – RegEx)
Finalmente, você pode usar Expressões Regulares para pesquisar padrões e contar o número de padrões correspondentes. As Expressões Regulares são feitas para isso, então é muito natural para a tarefa. Em Java, o Pattern
classe é usada para representar e compilar Expressões Regulares, e o Matcher
A classe é usada para encontrar e combinar padrões.
Usando RegEx, podemos codificar a invariância de pontuação na própria expressão, então não há necessidade de formatar externamente a string ou remover a pontuação, o que é preferível para textos grandes onde armazenar outra versão alterada na memória pode ser caro:
Pattern pattern = Pattern.compile("b%s(?!w)".format(targetWord));
Pattern pattern = Pattern.compile("bwants(?!w)");
Matcher matcher = pattern.matcher(searchText);
int wordCount = 0;
while (matcher.find())
wordCount++;
System.out.println(wordCount);
Isso também resulta em:
2
Referência de eficiência
Então, qual é o mais eficiente? Vamos executar um pequeno benchmark:
int runs = 100000;
long start1 = System.currentTimeMillis();
for (int i = 0; i < runs; i++) {
int result = countOccurencesWithSplit(searchText, targetWord);
}
long end1 = System.currentTimeMillis();
System.out.println(String.format("Array split approach took: %s miliseconds", end1-start1));
long start2 = System.currentTimeMillis();
for (int i = 0; i < runs; i++) {
int result = countOccurencesWithCollections(searchText, targetWord);
}
long end2 = System.currentTimeMillis();
System.out.println(String.format("Collections.frequency() approach took: %s miliseconds", end2-start2));
long start3 = System.currentTimeMillis();
for (int i = 0; i < runs; i++) {
int result = countOccurencesWithRegex(searchText, targetWord);
}
long end3 = System.currentTimeMillis();
System.out.println(String.format("Regex approach took: %s miliseconds", end3-start3));
Cada método será executado 100000 vezes (quanto maior o número, menor a variância e os resultados devido ao acaso, devido à lei dos grandes números). A execução deste código resulta em:
Array split approach took: 152 miliseconds
Collections.frequency() approach took: 140 miliseconds
Regex approach took: 92 miliseconds
No entanto – o que acontece se tornarmos a pesquisa mais cara computacionalmente, tornando-a maior? Vamos gerar uma frase sintética:
List possibleWords = Arrays.asList("hello", "world ");
StringBuffer searchTextBuffer = new StringBuffer();
for (int i = 0; i < 100; i++) {
searchTextBuffer.append(String.join(" ", possibleWords));
}
System.out.println(searchTextBuffer);
Isso cria uma string com o conteúdo:
hello world hello world hello world hello ...
Confira nosso guia prático e prático para aprender Git, com práticas recomendadas, padrões aceitos pelo setor e folha de dicas incluída. Pare de pesquisar comandos Git no Google e realmente aprender -lo!
Agora, se procurássemos por “hello” ou “world” – haveria muito mais correspondências do que as duas de antes. Como nossos métodos se saem agora no benchmark?
Array split approach took: 606 miliseconds
Collections.frequency() approach took: 899 miliseconds
Regex approach took: 801 miliseconds
Agora, a divisão de array sai mais rápido! Em geral, os benchmarks dependem de vários fatores – como o espaço de pesquisa, a palavra-alvo etc. e seu caso de uso pessoal pode ser diferente do benchmark.
Conselho: Experimente os métodos em seu próprio texto, observe os horários e escolha o mais eficiente e elegante para você.
Conclusão
Neste pequeno guia, vimos como contar ocorrências de palavras para uma palavra de destino, em uma string em Java. Começamos dividindo a string e usando um contador simples, seguido pelo Collections
classe auxiliar e, finalmente, usando expressões regulares.
No final, comparamos os métodos e notamos que o desempenho não é linear e depende do espaço de pesquisa. Para textos de entrada mais longos com muitas correspondências, a divisão de matrizes parece ser a mais eficiente. Experimente todos os três métodos por conta própria e escolha o mais eficiente.