Reconhecer repetições de palavras na String – java algoritmo stringbuffer

Pergunta:


Tenho um texto dentro de um StringBuffer e preciso verificar e marcar as palavras que aparecem mais de uma vez. A princípio utilizei uma fila circular de 10 posições, pois me interessa apenas palavras repetidas num “raio” de 10 palavras.
Vale observar que a marcação de palavras repetidas só pode ocorrer se as palavras repetidas estiverem em um raio de 10 palavras entre elas. Caso a palavras repetidas estejam a uma “distancia” de mais de 10 palavras, não devem ser marcadas.
O método Contem retorna null caso não haja repetição ou retorna a palavra que tem repetição.
String é apenas a variavel que contém o texto completo.

StringBuffer stringProximas = new StringBuffer();
String has = "";
Pattern pR = Pattern.compile("[a-zA-Zà-úÀ-Ú]+");
Matcher mR = pR.matcher(string);
while(mR.find()){
  word = mR.group();
  nextWord.Inserir(word);//inserir na lista
  has = nextWord.Contem();//verifica se há palavras iguais na lista
  //um if pra verificar se has é null ou nao
  //e aqui marca a palavra repetida, se has for diferente de null
  mR.appendReplacement(stringProximas, "");
  stringProximas.append(has);
}
public void Inserir(String palavra){
    if(this.list[9].equals("null")){
        if(this.list[0].equals("null")){
            this.list[this.fim]=palavra;
        }else{
            this.fim++;
            this.list[this.fim] = palavra;
        }
    }else{
        //inverte o apontador fim para a posição 0
        if(this.inicio == 0 && this.fim == 9){
            this.inicio++;
            this.fim = 0;
            this.list[this.fim] = palavra;
        }else if(this.inicio == 9 && this.fim == 8){//inverte o apontador inicio para posição 0
            this.inicio = 0;
            this.fim++;
            this.list[this.fim] = palavra;
        }else{
            this.inicio++;
            this.fim++;
            this.list[this.fim] = palavra;                    
        }
    }
}
public String Contem() throws Exception{
    for(int i=0;i<this.list.length;i++){
        for(int j=i+1;j<this.list.length;j++){
            if(this.list[i].equals(this.list[j]) && (!this.list[i].equals("null") || !this.list[j].equals("null"))){
                //nao pegar a mesma repetição mais de uma vez
                if(!this.list[i].equals("?")){
                    this.list[i] = "?";//provavelmente será retirado isso
                    return this.list[j];
                }
            }
        }
    }
    return "null";
}

Meu grande problema: caso eu encontre palavras repetidas, eu só consigo marcar a segunda ocorrência pois mesmo a primeira estando na fila, a variável word será a segunda e por causa while não consigo marcar a segunda.

Estou usando esse texto como exemplo:
Hoje em dia, é necessário ser esperto. O nosso dia a dia é complicado.
O método deve retornar por exemplo (coloquei como negrito aqui, mas não necessariamente é o jeito que marcar):
Hoje em dia, é necessário ser esperto. O nosso dia a dia é complicado.

Autor da pergunta Pacíficão

Caffé

Algoritmo:

Nesta solução eu quebrei todo o texto em tokens. Cada token ou é uma palavra ou é qualquer outra coisa entre as palavras (espaços, pontuações e outros símbolos).

Então eu percorro os tokens verificando se cada um deles é uma palavra que já existe entre as últimas palavras lidas, sendo que a quantidade de últimas palavras a comparar é limitada pelo raio especificado.

Se o token corresponder a uma palavra repetida dentro do raio, eu assinalo tanto este token que estou lendo agora como também aquela palavra repetida que já estava lá.

Por fim eu percorro novamente todos os tokens, reconstruindo o texto original e marcando as palavras cujos tokens haviam sido assinalados como palavras repetidas.

Código:

public static String assinalaPalavrasRepetidasEmUmRaio(String texto,
        String marcadorInicio, String marcadorFim, int qtdPalavrasRaio) {

    List<Token> tokens = new ArrayList<Token>();
    List<Token> palavrasNoRaio = new ArrayList<Token>();
    String palavraeNaoPalavraPattern = "\p{L}+|[^\p{L}]+";
    Matcher matcher = Pattern.compile(palavraeNaoPalavraPattern).matcher(texto);

    while (matcher.find()) {
        Token token = new Token(matcher.group());
        tokens.add(token);
        if (token.isPalavra() && palavrasNoRaio.contains(token)) {
            palavrasNoRaio.get(palavrasNoRaio.indexOf(token)).assinala();
            token.assinala();
        }
        if (token.isPalavra()) {
            palavrasNoRaio.add(token);
        }
        if (palavrasNoRaio.size() > qtdPalavrasRaio) {
            palavrasNoRaio.remove(0);
        }
    }
    StringBuilder textoReconstruido = new StringBuilder();
    for (Token token : tokens) {
        if (token.isAssinalado()) {
            textoReconstruido.append(marcadorInicio + token + marcadorFim);
        } else {
            textoReconstruido.append(token);
        }
    }
    return textoReconstruido.toString();
}

Classe Token:

Como observado no código acima, o próprio Token sabe se ele é ou não uma palavra, e também possui um flag que indica se ele foi assinalado.

class Token {
    private final String texto;
    private final boolean isPalavra;
    private boolean isAssinalado;

    public Token(String texto) {
        isPalavra = texto.matches("\p{L}+");
        this.texto = texto;
    }
    public boolean isPalavra() {
        return isPalavra;
    }
    public void assinala() {
        isAssinalado = true;
    }
    public boolean isAssinalado() {
        return isAssinalado;
    }
    @Override
    public int hashCode() {
        return texto.hashCode();
    }
    @Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof Token)) {
            return false;
        }
        return texto.equalsIgnoreCase(((Token)obj).texto);
    }
    @Override
    public String toString() {
        return texto;
    }
}

Os métodos hashCode e equals não são consumidos diretamente pelo meu código, mas eles são usados pela implementação do Java para list.contains e list.indexOf, sendo que o hashCode ajuda a acelerar a busca e o equals é a comparação para saber se o item é igual ao que se está pesquisando.

Existem várias técnicas para fazer um hash code que auxilie na performance. Neste caso eu simplesmente retorno o hash code do texto, pois é o texto que eu comparo no equals para dizer se um token é igual a outro. Repare que se o hashCode retornar Zero para todos os Tokens, ainda assim a busca funcionará, a questão é mesmo a performance – vale a pena uma leitura aprofundada sobre hash codes.

Código consumidor:

E este é o teste unitário:

@Test
public void assinalaRepetidasEmUmRaio() {

  String texto = "Dia! É bom ser esperto, não é mesmo? O nosso dia a dia é complicado.";

  String esperado = "Dia! [É] bom ser esperto, não [é] mesmo? O nosso [dia] a [dia] é complicado.";

  String obtido = ProcessadorTexto.assinalaPalavrasRepetidasEmUmRaio(texto, "[", "]", 5);

  assertEquals(esperado, obtido);
}

Repare que são detectadas como repetidas mesmo as palavras com captalização diferente (“É” e “é”), o que era uma deficiência da minha primeira resposta, trazida à minha atenção pela resposta do @mgibsonbr. Quem faz o truque aí é o método Token.equals que é usado para verificar se o token já está na lista de palavras (palavras.contains(token)).

Repare ainda que a primeira palavra “dia” e a última palavra “é” não foram assinaladas porque suas repetições mais próximas estão a uma distância superior ao raio especificado.

Sobre as expressões regulares utilizadas:

A expressão regular (regex) que usei para encontrar cada palavra unicode é p{L}+ pois o simples w+ em Java se perde com palavras acentuadas.

E a regex que eu usei para obter todo o resto que não é palavra foi a negação da outra expressão, ou seja: [^p{L}]+. Isso porque o Java encontra também os caracteres acentuados quando usada a regex non word W+.

E para obter todos os tokens de uma vez (palavras e não palavras) eu usei ao mesmo tempo as duas expressões regulares separadas pelo símbolo | (pipe), que pode ser etendido como “um ou outro”, por exemplo: X|Y = “encontre tanto X quanto Y”.

Solução:

Usando expressões regulares dá pra resolver com um código bem expressivo, pequeno e com poucos ifs – na verdade apenas 1 if e apenas 1 loop:

public String assinalaRepetidas(String texto, String marcadorInicio, 
                                            String marcadorFim, int qtdPalavrasAnalisar) {

    String palavraInteiraPattern = "\p{L}+"; 
    Pattern p = Pattern.compile(palavraInteiraPattern);
    Matcher matcher = p.matcher(texto);

    ArrayList<String> palavras = new ArrayList<String>();
    ArrayList<String> palavrasRepetidas = new ArrayList<String>();

    while (matcher.find() && palavras.size() < qtdPalavrasAnalisar) {

        String palavra = matcher.group();

        if (palavras.contains(palavra) && !palavrasRepetidas.contains(palavra)) {
            texto = texto.replaceAll(
                    String.format("\b%s\b", palavra), 
                    String.format("%s%s%s", marcadorInicio, palavra, marcadorFim));

            palavrasRepetidas.add(palavra);
        }
        palavras.add(palavra);
    }
    return texto;
}

E isso é tudo! Fim.

Abaixo, alguma explicação e também o código consumidor.

Explicando a solução:

Eu usei expressão regular obter cada palavra no texto, ignorando espaços, parênteses, símbolos, vírgulas e outras pontuações que não sejam palavras de verdade. A expressão regular para fazer isso em Java em um texto com acentuação (usando unicode UTF-8) é p{L}+.

No mesmo loop que eu obtenho as palavras encontradas pela expressão regular, eu já substituo a palavra repetida por ela mesma, envolvendo-a pelos marcadores.

O código consumidor (teste unitário) ficou assim:

@Test
public void assinalaPrimeirasPalavrasRepetidas() {
  String texto = "Hoje em dia, é necessário ser esperto. O nosso dia a dia é complicado.";
  String esperado = "Hoje em [dia], é necessário ser esperto. O nosso [dia] a [dia] é complicado.";

  assertEquals(esperado, new AnalisaTexto().assinalaRepetidas(texto, "[", "]", 10));
}

Apesar de a pergunta descrever que quer apenas as 10 primeiras palavras, o exemplo de resultado esperado parece considerar todas elas. Então eu adicionei uma assinatura que dispensa o “raio” de palavras a analisar:

public String assinalaPalavrasRepetidas(String texto, String marcadorInicio, String marcadorFim) {
    return assinalaRepetidas(texto, marcadorInicio, marcadorFim, Integer.MAX_VALUE);
}

Usando este outro método, como mais de 10 palavras são analisadas, o “é” também é identificado como repetido:

@Test
public void assinalaTodasPalavrasRepetidas() {
  String texto = "Hoje em dia, é necessário ser esperto. O nosso dia a dia é complicado.";
  String esperado = "Hoje em [dia], [é] necessário ser esperto. O nosso [dia] a [dia] [é] complicado.";

  assertEquals(esperado, new AnalisaTexto().assinalaPalavrasRepetidas(texto, "[", "]"));
}

Por fim, observe que usei expressões regulares também na hora de substituir as palavras pelos seus equivalentes assinalados. Observe a regex no método texto.replaceAll. Do contrário, uma parte de outra palavra que coincidisse também seria assinalada. Por exemplo, em “ser servidor” seria assinalado “[ser] [ser]vidor”.

O teste que comprova a eficácia deste pequeno cuidado é:

@Test
public void assinalaApenasPalavraInteira() {

    String texto = "Hoje em dia, pode ser necessário servir ao ser esperto.";
    String esperado = "Hoje em dia, pode [ser] necessário servir ao [ser] esperto.";

    assertEquals(esperado, new AnalisaTexto().assinalaPalavrasRepetidas(texto, "[", "]"));
}

O próprio código é explicativo. Basicamente o que fiz foi:

  1. Criar uma lista com as palavras repetidas;
  2. Percorrer essa lista e buscar cada palavra da lista no texto original;
  3. Trocar a palavra por ela mesma mas com a marcação.

Fiz a marcação no texto com <b></b> ao redor das palavras repetidas.

Tendo a lista das repetidas fica fácil, pois a função replace() faz quase todo trabalho: busca as palavras no texto original e troca pela marcação.

Principal

Ideone ide = new Ideone();
StringBuffer texto = new StringBuffer("Hoje em dia, é necessário ser esperto. O nosso dia a dia é complicado.");
List<String> palavrasRepetidas = ide.pegaRepetidas(texto);

String saida = texto.toString().replace(palavrasRepetidas.get(0), "<b>"+palavrasRepetidas.get(0)+"</b>");

for (int i=1; i < palavrasRepetidas.size(); i++) {
  saida = saida.replace(palavrasRepetidas.get(i), "<b>"+palavrasRepetidas.get(i)+"</b>");
}
System.out.println(saida);

pegaRepetidas()

/**Retorna uma lista com as palavras que aparecem mais de uma vez no texto*//
private static List<String> pegaRepetidas(StringBuffer texto) {
    String textoFormatado = texto.toString().replaceAll("[,.!]", ""); //Retira pontos e vírgulas
    StringTokenizer st = new StringTokenizer(textoFormatado);

    List<String> palavrasRepetidas = new ArrayList<>();

    while (st.hasMoreTokens()) {
        String palavra = st.nextToken();
        if (contaPalavra(palavra, textoFormatado) > 1) { // Se palavra aparece mais de uma vez
            if ( !palavrasRepetidas.contains(palavra) ) { // Se ela ainda não se encontra na lista de repetidas
                palavrasRepetidas.add(palavra);
            }
        }
    }

    return palavrasRepetidas;
}

contaPalavras()

/** Retorna o número de vezes que a 'palavra' aparece no 'texto' */
private static int contaPalavra(String palavra, String texto) {
    StringTokenizer st = new StringTokenizer(texto);
    int count = 0;
    while (st.hasMoreTokens()) {
        if (st.nextToken().compareTo(palavra) == 0) {
            count++;
        }
    }

    return count;
}

Veja funcionando no ideone: http://ideone.com/n8xLlo

Decidi reimplementar do zero. Os motivos:

  • Não depender de implementações customizadas de lista;
  • Não depender de Strings com valores especiais e arbitrários;
  • Não precisar de números hardcoded ou matemática complicada;
  • Não precisar de expressões regulares aonde uma abordagem mais simples resolve;
  • Delegar ao próprio Java determinar o que é uma letra ou não de acordo com a sua implementação do padrão Unicode.

E aqui está o resultado. Comentários explicativos no código:

import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Set;

/**
 * @author Victor
 */
public class BuscaPalavras {

    private static final Locale PT_BR = new Locale("PT", "BR");

    public static Set<String> palavrasRepetidas(int raio, String texto) {
        // Palavras repetidas dentro do raio já encontradas. Usa um Set para eliminar duplicatas automaticamente.
        Set<String> palavrasRepetidas = new LinkedHashSet<>(50);

        // Lista contendo as últimas palavras encontradas. O tamanho máximo da lista é igual ao raio.
        List<String> ultimasPalavras = new LinkedList<>();

        // Usa para guardar a plavra que está se formanado a medida que os caracteres são lidos.
        StringBuilder palavra = null;

        // Transforma o texto todo em um array.
        char[] caracteres = texto.toCharArray();
        int tamanho = caracteres.length;

        // Itera cada posição do array até uma depois da última (importante).
        for (int i = 0; i <= tamanho; i++) {
            // Se o caractere i do texto for uma letra, acrescenta ele no StringBuilder.
            // Se estiver na posição depois da última, não entrará no if e seguirá para o else-if.
            if (i < tamanho && Character.isLetter(caracteres[i])) {
                // Cria o StringBuilder caso a palavra esteja começando agora.
                if (palavra == null) palavra = new StringBuilder(20);
                palavra.append(caracteres[i]);

            // Caso contrário, se uma palavra acabou de ser encerrada...
            } else if (palavra != null) {
                // Retira do StringBuilder e converte para maiúsculas.
                String novaPalavra = palavra.toString().toUpperCase(PT_BR);

                // Se for uma das últimas palavras de acordo com o raio, acrescenta na lista de palavras repetidas.
                if (ultimasPalavras.contains(novaPalavra)) palavrasRepetidas.add(novaPalavra);

                // Faz a lista de últimas palavras andar.
                if (ultimasPalavras.size() >= raio) ultimasPalavras.remove(0);
                ultimasPalavras.add(novaPalavra);

                // Terminou a palavra. Volta para null para que outra palavra se inicie depois.
                palavra = null;
            }
        }
        return palavrasRepetidas;
    }

    // Para testar o método palavrasRepetidas.
    public static void main(String[] args) {
        String texto = "O rato roeu a roupa do rei de Roma e a rainha roeu o resto."
                + " Quem mafagafar os mafagafinhos bom amafagafigador será."
                + " Será só imaginação? Será que nada vai acontecer? Será que é tudo isso em vão?"
                + " Será que vamos conseguir vencer? Ô ô ô ô ô ô, YEAH!"
                + " O pato faz Quack-quack!"
                + " Quem é que para o time do Pará?";

        System.out.println(palavrasRepetidas(10, texto));
    }
}

Saída do método main:

[A, ROEU, SERÁ, QUE, Ô, QUACK, O]

Não sou muito bom em java, então aceito correções neste código,
porém acho que cheguei no resultado que você deseja:

Veja ele rodando aqui no Ideone:

http://ideone.com/8YvDnp

import java.util.*;
import java.lang.*;
import java.io.*;

enum TokenKind
{
    WordSeparator,
    Word
}

class Token
{
    int _start;
    int _end;
    String _text;
    TokenKind _kind;

    public String getText()
    {
        return _text;
    }

    public void setText(String value)
    {
        _text = value;
    }

    public void setStart(int value)
    {
        _start = value;
    }

    public void setEnd(int value)
    {
        _end = value;
    }

    public int getStart()
    {
        return _start;
    }

    public int getEnd()
    {
        return _end;
    }

    public TokenKind getKind()
    {
        return _kind;
    }

    public void setKind(TokenKind value)
    {
        _kind = value;
    }
}

class LinearRepeatSearchLexer
{
    StringBuffer _text;
    int _position;
    char _peek;

    public LinearRepeatSearchLexer(StringBuffer text)
    {
        _text = text;
        _position = 0;
        _peek = (char)0;
    }

    public Token nextToken()
    {
        Token ret = new Token();
        char peek = PeekChar();

        if(isWordSeparator(peek))
        {
            ret.setStart(_position);
            readWordSeparator(ret);
            ret.setEnd(_position - 1);
            return ret;
        }
        else if(isLetterOrDigit(peek))
        {
            ret.setStart(_position);
            readWord(ret);
            ret.setEnd(_position - 1);
            return ret;
        } 
        else if(peek == (char)0)
        {
            return null;
        }
        else
        {
            // TODO: 
            //  caracteres não identificados
            //  ou você pode simplificar o readWord
            return null;
        }
    }

    void readWordSeparator(Token token)
    {
        char c = (char)0;
        StringBuffer tokenText = new StringBuffer();
        while(isWordSeparator(c = PeekChar()))
        {
            tokenText.append(c);
            MoveNext(1);
        }
        token.setText(tokenText.toString());
        token.setKind(TokenKind.WordSeparator);
    }

    void readWord(Token token)
    {
        char c = (char)0;
        StringBuffer tokenText = new StringBuffer();
        while(isLetterOrDigit(c = PeekChar()))
        {
            tokenText.append(c);
            MoveNext(1);
        }
        token.setText(tokenText.toString());
        token.setKind(TokenKind.Word);
    }

    boolean isWordSeparator(char c)
    {
        // TODO: outros separadores aqui
        return c == ' ' ||
            c == 't' ||
            c == 'n' ||
            c == 'r' ||
            c == ',' ||
            c == '.' || 
            c == '-' || 
            c == ';' || 
            c == ':' ||
            c == '=' ||
            c == '>';
    }

    boolean isLetterOrDigit(char c)
    {
        // TODO: outras letras aqui
        return (c >= 'a' && c <= 'z') ||
            (c >= 'A' && c <= 'Z') ||
            (c >= '0' && c <= '9') ||
            (c >= 'à' && c <= 'ú') ||
            (c >= 'À' && c <= 'Ú') ||
            c == '_';
    }

    char PeekChar()
    {
        if(_position < _text.length())
            return _text.charAt(_position);
        return (char)0;
    }

    void MoveNext(int plus)
    {
        _position += plus;
    }
}

class LinearRepeatSearch
{
    StringBuffer _text;
    int _radius;

    public LinearRepeatSearch(StringBuffer text, int radius)
    {
        _text = text;
        _radius = radius;
    }

    public LinearRepeatSearch(String text, int radius)
    {
        this(new StringBuffer(text), radius);   
    }

    public List<Token> getRepeatedWords()
    {
        // ler todos os tokens
        ArrayList<Token> ret = new ArrayList<Token>();
        ArrayList<Token> readed = new ArrayList<Token>();
        LinearRepeatSearchLexer lexer = new LinearRepeatSearchLexer(_text);
        Token token = null;
        while((token = lexer.nextToken()) != null)
        {
            if(token.getKind() == TokenKind.Word)
                readed.add(token);
        }

        // localizar repetições a partir do raio
        // PERF:
        //      este laço pode ser melhorado em termos de performance
        //      pois há comparações repetidas aqui
        int size = readed.size();
        for(int x = 0; x < size; x++)
        {
            Token a = readed.get(x);
            for(int y = Math.max(0, x - _radius); y < size && (y - x) < _radius; y++)
            {
                if(x == y) continue;
                Token b = readed.get(y);
                if(a.getText().equals(b.getText()))
                {
                    ret.add(a);
                    break;
                }
            }
        }

        return ret;
    }
}

class Ideone
{
    public static void main (String[] args) throws java.lang.Exception
    {
        // your code goes here

        StringBuffer input = new StringBuffer("Hoje em dia, é necessário ser esperto. O nosso dia a dia é complicado.");
        StringBuffer output = new StringBuffer();
        LinearRepeatSearch searcher = new LinearRepeatSearch(input, 10);
        List<Token> spans = searcher.getRepeatedWords();
        int listSize = spans.size();
        int position = 0;
        for(int x = 0; x < listSize; x++)
        {
            Token item = spans.get(x);
            output.append(input.substring(position, item.getStart()));
            output.append("<b>");
            output.append(item.getText());
            output.append("</b>");
            position = item.getEnd() + 1;
        }
        if(position < input.length())
        {
            output.append(input.substring(position));
        }
        System.out.println(output.toString());
    }
}

Resultado do código:

Hoje em dia, é necessário ser esperto. O nosso dia a dia é complicado.

Uma solução bastante simples*, usando split e dois conjuntos:

public static String marcarRepetidas(String s, String prefixo, String sufixo) {
    Set<String> palavras = new HashSet<String>();
    Set<String> palavrasRepetidas = new HashSet<String>();

    // Acha o conjunto de palavras repetidas
    for ( String palavra : s.split("[^a-zA-Zà-úÀ-Ú]+") ) {
        palavra = palavra.toLowerCase();
        if ( palavra.length() > 0 && palavras.contains(palavra) )
            palavrasRepetidas.add(palavra);
        palavras.add(palavra);
    }

    // Marca cada uma dessas palavras no texto (envolvendo-as num prefixo e sufixo)
    for ( String palavra : palavrasRepetidas )
        s = s.replaceAll("(?<![a-zA-Zà-úÀ-Ú])(?iu)(" + palavra + ")(?![a-zA-Zà-úÀ-Ú])",
                         prefixo + "$1" + sufixo);

    // No Java 8 é mais simples (uma única chamada do replaceAll):
    // String juncao = String.join("|", palavrasRepetidas);
    // s = s.replaceAll("(?<![a-zA-Zà-úÀ-Ú])(?iu)(" + juncao + ")(?![a-zA-Zà-úÀ-Ú])",
    //                  prefixo + "$1" + sufixo);

    return s;
}

Exemplo no Ideone. Esse replaceAll no final merece uma explicação: antes de substituir uma palavra no texto, é importante se certificar que ela é mesmo uma palavra, e não uma substring de outra palavra. Para isso usei dois lookarounds negativos, um para ver se ela não é precedida de uma letra, e outra para ver se não é sucedida. O (?iu) é para ignorar a capitalização, e o grupo de captura é para que a palavra seja substituída pela versão marcada mas sem alterar sua capitalização. Exemplo:

Hoje em dia, é necessário ser esperto. O nosso dia a dia é complicado. Zé. diafragma. Dia. É. Dia.

Saída:

Hoje em dia, é necessário ser esperto. O nosso dia a dia é complicado. Zé. diafragma. Dia. É. Dia.

* Essa resposta tem como objetivo a simplicidade, não a eficiência; um método “manual” (i.e. onde cada chamada custosa da API – como as regexes – fosse substituída por um loop explícito e então otimizado), se aproveitando do StringBuilder, etc poderia ter melhor performance, se esse requisito for importante no seu caso particular.

Olá. Eu fiz uma implementação bem simples.

Essencialmente as palavras são contadas e um mapa é populado com a palavras e o número de ocorrências. Usei a palavra como chave e a quantidade como valor.

Depois eu varri o mapa, sobrescrevendo as palavras que ocorrem mais de uma vez.

Em java 8 o código ficaria mais limpo e, com certeza, a implementação pode ser melhorada.

Veja o código:

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.StringTokenizer;


public class WordCount {
    Map<String, Integer> counter = new HashMap<String, Integer>();

    /**
     * @param args
     */
    public static void main(String[] args) {
        new WordCount().count();
    }

    private void count() {
        String string = "Hoje em dia, é necessário ser esperto. O nosso dia a dia é complicado.";

        StringTokenizer token = new StringTokenizer(string, " .,?:"); //caracateres que não interessam

        while (token.hasMoreTokens()) {
            String s = token.nextToken();
            count(s);
            System.out.println(s);
        }

        System.out.println(counter);
        print(string);
    }

    private void count(String s) {
        Integer i = this.counter.get(s);
        this.counter.put(s, i == null ? 0 : ++i);
    }

    private void print(String s) {
        for (Entry<String, Integer> e : this.counter.entrySet()) {
            if (e.getValue() > 0) {
                s = s.replaceAll(e.getKey(), String.format("<b>%s</b>", e.getKey()));
            }
        }

        System.out.println(s);
    }
}

Ok, ok, ok, não é Java…

#!/usr/bin/perl
use strict;
use utf8::all;

my %conta;
my $s="Hoje em dia, é necessário ser esperto. O nosso dia a dia é complicado.";

for($s=~ m{(w+)}g ) { $conta{$_}++ }
$s =~ s{(w+)}{ if($conta{$1}>1){ "<b>$1</b>"} else {"$1"}}eg;

print $s;

Fonte

Related Posts:

Qual a diferença entre AppCompatActivity e Activity? – android android-activity
Pergunta: Qual a diferença da AppCompatActivity para Activity ? A partir de qual versão a AppCompatActivity foi adicionada ao Android? Autor da pergunta Luhhh A diferença reside ...
Como abreviar palavras em PHP? – php string
Pergunta: Possuo informações comuns como nome de pessoas e endereços, e preciso que elas contenham no máximo 30 caracteres sem cortar palavras. Exemplo: 'Avenida Natalino João Brescansin' ...
Qual é a finalidade de um parêntese vazio numa declaração Lambda? – c# expressões-lambda característica-linguagem
Pergunta: Criei um exemplo de uma declaração Lambda sem argumentos, entretanto, estou com duvidas referente a omissão do parêntese vazio () na declaração. Veja o exemplo: class ...
Boas práticas para URI em API RESTful – api rest restful
Pergunta: Estou com dúvida em relação às URIs de alguns recursos da api que estou desenvolvendo. Tenho os recursos projetos e atividades com relação 1-N, ...
Dúvidas sobre a integração do MySQL com Java – java mysql netbeans
Pergunta: Estou criando um sistema no NetBeans, utilizando a linguagem Java e o banco de dados MySQL. Escrevi o seguinte código para realizar a conexão ...
Qual é a finalidade da pasta Model do framework Inphinit? – php inphinit
Pergunta: No Inphinit micro-framework existe a pasta Model que fica dentro da pasta application, e nela é onde ficam as classes, mas eu estou muito ...
Uso do ‘@’ em variáveis – javascript typescript coffeescript
Pergunta: Vejo em algumas linguagens que compilam para javascript, como TypeScript e CoffeeScript, o uso do @ em variáveis, como também, casos em que o ...
Qual tamanho máximo um arquivo JSON pode ter? – json arquivo
Pergunta: Vou dar um exemplo para conseguir explicar minha duvida: Preciso recuperar informação de imagens vindas de uma API, esse banco de imagens me retorna JSON's ...
O que é Teste de Regressão? – terminologia engenharia-de-software testes
Pergunta: Na matéria de Teste de Software o professor abordou um termo chamado Teste de Regressão, isto dentro da disciplina de teste de software. Sendo ...
O que é um construtor da linguagem? – php característica-linguagem
Pergunta: Em PHP, já li e ouvi várias vezes a respeito dos Construtores da Linguagem. Os casos que sempre ouvi falar deles foi em casos ...
Função intrínseca para converter numérico para string – cobol
Pergunta: Estou a tentar saber se existe alguma função intrínseca do COBOL para converter um data numérico para string sem precisar usar a cláusula REDEFINES: ( ...
Porque usar implements? – java android
Pergunta: Qual a diferença entre usar btn.setOnClickListener(new OnClickListener() { e public class MainActivity extends Activity implements OnClickListener{ Estive fazendo um curso de Android e meu professor falou que ...
O que é XHTML e quando deve ser usado? – html xml xhtml
Pergunta: O que eu sei é que o XHTML precisa ser XML válido. Isso implica, por exemplo, que todas as tags precisam ser fechadas. Por ...
Uma placa aceleradora de vídeo pode melhorar o desempenho não-gráfico? [fechada] – desempenho
Pergunta: Para desenvolver em Ruby on Rails, eu utilizo aqui uma máquina virtual do VirtualBox com Ubuntu Server 14.04 sem interface gráfica instalada. Recentemente descobri uma ...
Concat() VS Union() – c# .net
Pergunta: Qual a diferença entre Concat() e Union() ? Quando usar Concat() e quando usar Union() ? Somente pode ser usado em list ? ...

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *