1. Introdução

Na linguagem Java, um mapa (map) é um objeto que associa (ou “mapeia”) chaves com valores. Cada chave é sempre associada a, no máximo, um valor e não podem existir chaves duplicadas. A Figura 1 mostra um exemplo em que as chaves são nomes de pessoas e os valores são os números de telefone das mesmas.

Mapa associando nomes (chave) e telefones (valor)

Figura 1: Mapa associando nomes (chave) e telefones (valor)

Existem inúmeras aplicações práticas para os mapas. Este artigo apresenta uma das mais interessantes: usar este tipo de objeto com o objetivo de descobrir as palavras presentes em um determinado arquivo texto e computar as suas frequências (número de ocorrência de cada uma delas).

2. Computando as Frequências

A Listagem 1 apresenta o programa Java que utiliza um objeto do tipo java.util.HashMap para realizar a contagem de frequência de palavras. A explicação sobre o funcionamento do programa é apresentada através de comentários colocados dentro do código e em explicações adicionais apresentadas na parte final deste artigo.

Listagem 1: Classe “ContaPalavras”


import java.io.BufferedReader;
import java.io.FileReader;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * classe ContaPalavras - recebe como entrada um arquivo texto, identifica as
 * diferentes palavras e contabiliza as frequências.
 * 
 * uso: java ContaPalavras arquivo_texto
 * 
 * @author Eduardo Correa
 * 
 */
public class ContaPalavras {

	public static void main(String[] args) throws Exception {

	//-------------------------------------------------------
	// (0) declaração/inicialização de variáveis
	//-------------------------------------------------------
		
	String curLine; //recebe cada linha lida do arquivo texto
		
	Map<String,Integer> mapPalavras; //mapa: Palavra -> Frequencia  
						//usado para contabilizar as 
						//frequencias das palavras
		
	mapPalavras = new HashMap<String,Integer>();
		
		
	//-------------------------------------------------------
	// (1) abre o arquivo texto
	//-------------------------------------------------------
		
	//(1.1) testa se nome do arq. texto foi passado na chamada do programa
        if (args.length != 1) {
            System.err.println("ERRO: eh preciso especificar o nome do arquivo");
            System.err.println("Uso: java ContaPalavras arquivo_texto");
            System.exit(1);
        }

	//(1.2) abre o arquivo
        FileReader txtFile = new FileReader(args[0]);
        BufferedReader txtBuffer = new BufferedReader(txtFile);

	//-------------------------------------------------------
	// (2) loop que processa cada linha do arquivo texto
	//-------------------------------------------------------

        //(2.1) pega a primeira linha do arquivo
        curLine = txtBuffer.readLine();
      
        
        while (curLine != null) {
        	
    		//-------------------------------------------------------
        	//(2.2) quebra a linha em tokens (palavras) utilizando 
        	//      expressão regular. 
        	//
        	//      O programa usa uma forma simplificada p/ obter os tokens.
        	//      São considerados tokens:
        	//      - uma sequência de 1 a n números
        	//      - uma sequência de 1 a n letras
    		//-------------------------------------------------------

        	//primeiro converte tudo para minúsculo
        	String minusculo = curLine.toLowerCase();
        	
        	//depois aplica a expressão regular
        	Pattern p = Pattern.compile("(\\d+)|([a-záéíóúçãõôê]+)");
        	Matcher m = p.matcher(minusculo);

        	
    		//-------------------------------------------------------
        	//(2.3) IMPORTANTE: neste loop pegamos cada palavra 
        	//                  e atualizamos o mapa de frequências
    		//-------------------------------------------------------
        	
        	while(m.find())
        	{
        	  String token = m.group(); //pega um token   
        	  Integer freq = mapPalavras.get(token); //verifica se esse 
        	  					     //token já está no mapa	
				
				if (freq != null) { //se palavra existe, atualiza a frequencia
					mapPalavras.put(token, freq+1);
				}
				else { // se palavra não existe, insiro com um novo id e freq=1.
					mapPalavras.put(token,1);
				}
        	}
        	
		//pega a próxima linha do arquivo
        	curLine = txtBuffer.readLine();
        }
        
        txtBuffer.close();

	//-------------------------------------------------------
	// (3) imprime o mapa de frequencias
	//-------------------------------------------------------
	 for (Map.Entry<String, Integer> entry : mapPalavras.entrySet()) {
		System.out.println(entry.getKey() + "\tfreq=" + entry.getValue());
	 }

   }

}

Para testar a execução do programa, utilizaremos o arquivo “teste.txt” que contém o texto de uma notícia publicada em um jornal. O conteúdo do arquivo é apresentado na Figura 2.

Arquivo teste.txt

Figura 2: Arquivo teste.txt

A execução do programa sobre esse arquivo texto produzirá o resultado mostrado na Figura 3:

Resultado do Processamento

Figura 3: Resultado do Processamento

No programa apresentado, as seções 2.2 e 2.3 contêm os trechos de código mais importantes. Por isso vamos agora comentar um pouco essas seções. Na seção 2.2, o primeiro passo é converter a linha lida do arquivo para minúsculo. Após isso ser feito, utilizamos a classe “Pattern” para definir a expressão regular que será utilizada para “quebrar” uma linha lida do arquivo em um conjunto de palavras (ou “tokens” - termo comumente utilizado na mineração de texto). A classe “Matcher” é responsável por aplicar (ou executar) essa expressão regular.

O programa da Listagem 1 utiliza a seguinte expressão regular:

(\\d+)|([a-záéíóúçãõôê]+)")

Esta expressão indica que consideraremos uma palavra qualquer sequência de números (ex: 1970, 2013, 33, 0, etc.) ou qualquer sequência de letras (ex: “jogo”, “a”, “dribles”, etc.). Não é um método perfeito, pois realiza separações erradas em alguns casos. Por exemplo: no caso da frase “plataforma P20”, a expressão regular faz a separação em três tokens, “plataforma”, “p” e “20” ao invés do correto, que seria apenas dois (“plataforma” e “p20”). Outro problema ocorre em palavras que possuem hífen, como “couve-flor”, que seria quebrada em 2 tokens (“couve” e “flor”). De qualquer forma, no geral, a expressão regular apresenta bom desempenho e a vantagem de ser simples.

Seguindo com a explicação do programa, na seção 2.3 temos um loop que percorre cada token (palavra) identificado em uma linha do arquivo como o objetivo de criar o mapa de palavras. Nosso mapa terá a estrutura similar a mostrada no esquema da Figura 4. As chaves são as palavras e os valores a frequência de cada uma delas (é um mapa String -> Integer).

Estrutura do objeto mapPalavras

Figura 4: Estrutura do objeto mapPalavras

No loop implementado na seção 2.3, realizamos o seguinte processamento para cada token. Primeiro verificamos se o mesmo já foi inserido no HashMap (objeto “mapPalavras”) com o uso do método “get”. Depois basta usar o método “put” para atualizar o mapa. O método “put” tem a vantagem de ser bastante versátil, pois podemos utilizá-lo tanto para inserir um novo elemento no mapa como para atualizar um elemento já existente. Observe que, em nosso programa, quando uma palavra ainda não está no mapa, utilizamos o método “put” para inseri-la com frequência = 1. Quando a palavra já está armazenada no mapa, utilizamos igualmente o método “put”, mas desta vez para incrementar a frequência da mesma.

Finalizando o programa, na seção 3 percorremos todos os elementos do nosso mapa, imprimindo as chaves (palavra) e os valores (frequência).

Até a próxima!