26 julho 2009

SCJP - 06 - Expressões Regulares

Quando eu vi isso pela primeira vez, eu gostei pra caramba. Usei isso bastante nos meus trabalhos de faculdade, porque costumávamos ler as entradas para nossos trabalhos de um arquivo de texto. Eram vertices e arestas de um grafo, pontos de controle para uma superfície em cálculo numérico, enfim, bastante coisa. E depois, o pouco conhecimento de expressões regulares serviu para validação e manipulação de strings em javascript.


Uma breve revisão de expessões regulares


Algumas coisas a gente pega fácil, como por exemplo, os metacaracteres. Existem alguns que precisamos entender para o exame. Temos o \d, que "casa" com todos os dígitos, tem o \s, que "casa" com espaço em branco, e o \w, que "casa" com todos os caracteres alfanuméricos (letras, números e o "underscore", o "_"). Tem o ponto (.) que funciona como curinga, ou seja, qualquer caractere "casa" com ele.

Tem os quantificadores, que servem para dizermos quantas vezes queremos que um padrão se repita na nossa expressão regular. Existe o +, que permite repetição de uma ou mais vezes aquele padrão, o *, para zero ou mais vezes, e o ?, para zero ou uma vez.

Com essas facilidades, nós conseguimos criar expressões como 0[xX]([a-zA-Z\d])+, que casa com números hexadecimais, ou \d\d\d\d[-\s]\d\d\d\d, que casa com telefones de 8 dígitos, separados ao meio por um hífem (-) ou um espaço em branco.

Você pode fazer um programinha que lhe ajude a brincar com casamento de padrões com duas classes do pacote java.util.regex: Pattern e Matcher.

import java.util.regex.*;

public class RegexMatcher {
    public static void main(String[] args) {
        if (args.length != 2) {
            System.out.println ("Passe padrão e entrada, " 
                + "nessa ordem, como parametro");
        } else {
            System.out.println("Padrao: " + args[0]);
            System.out.println("Entrada: " + args[1]);

            Pattern pattern = Pattern.compile(args[0]);
            Matcher matcher = pattern.matcher(args[1]);

            while (matcher.find()) {
                System.out.println("Inicio em: " 
                    + matcher.start() 
                    + " - Encontrato: " 
                    + matcher.group());
            }
        }
    }
}


Bem, mas não foi bem essa a intenção desse post. Normalmente, eu crio estes posts da SCJP para coisas que eu ainda não sabia, ou coisas importantes que eu precisava frizar. Existem construções de expressões regulares conhecidas como gulosas (greed), relutantes (reluctant) e possessivas (possessive).

Pegue a entrada xxxxxyxyxyxxxyxxyxyxx e o padrão (.)*xx e veja o que o seu programa te retorna. Você teria, pelo menos, quantas possibilidades?

Uma coisa engraçada que acontece, é que quando eu vi isso pela primeira vez, eu tentava pensar em casamento de padrões como aquele problema conhecido de encontrar quantos triângulos existiam na imagem. A gente costuma usar os triângulos menores para formar triângulos maiores. Mas em expressões regulares, esse não é o default.


os resultados encontrados não são usados para formar novos resultados, não por default. Então, eu acho que teríamos apenas esses resultados:

xx
xx
xyxyxyxx
xyxx
yxyxx

Mas o seu padrão "engoliu" a entrada inteira (lá ele), não foi? Isso porque o programa olhou para toda a entrada antes de definir um "xx" como sufixo.

  • Usar ? é guloso. Usar ?? é relutante
  • Usar + é guloso. Usar +? é relutante
  • Usar * é guloso. Usar *? é relutante

Assim, se vc usar o padrão (.)*?xx como padrão, os resultados que vc vai encontrar vão ser diferentes de quando vc usou o padrão anterior (exatemente os mesmos resultados que eu mencionei acima). Usando expressões relutantes, a entrada inteira não é utilizada antes de avaliar o padrão.

Problemas entre expressões regulares e strings


Quando for criar expressões regulares, frequentemente você usará construções como \d, \s e \w. Provavelmente, vai criar construções como:

String patternString = "\d"; 
Pattern pattern = Pattern.compile(patternString);

Quando você tentar compilar isso, vai dar um erro. Por quê? o caractere "\" é usado em java como flag para uma sequência de escape, como "\n" por exemplo, que nos dá um enter. Daí, quando o compilador enxergar o "\d", vai tentar achar alguma sequência de escape pra isso e não vai encontrar, o que gerará o erro. Por isso, ao invés de "\d", você terá que usar um "\\d". O "\\" serve como uma sequência de escape para o "\".

No nosso programa anterior, usamos os argumentos passados via linha de comando diretamente na classe Pattern e Matcher. Se fossemos armazená-los em uma string, para depois incluirmos em tais classes, talvez precisássemos utilizar sequências de escape. Note também que se você precisar de espaços em branco na string usada como entrada de dados, envolva tal string com aspas (").

Classes Pattern e Matcher


Agora vamos analizar o programa anterior que nós usamos para testar as nossas expressões regulares. A classe Pattern serve para representar os padrões usados por nós. Para criar uma instância de Pattern, usamos o método estático Pattern.compile(). A classe Matcher serve para englobar a máquina de avaliação das expressões regulares, e para criar uma instância, você usa o método Pattern.macher(). O método Matcher.find() avalia/consome a expressão de entrada de dados e diz se ainda tem algum trecho que case com o padrão (retorna true, nesse caso).

import java.util.regex.*;

public class RegexMatcher {
    public static void main(String[] args) {
        if (args.length != 2) {
            System.out.println ("Passe padrão e entrada, " 
                + "nessa ordem, como parametro");
         } else {
            System.out.println("Padrao: " + args[0]);
            System.out.println("Entrada: " + args[1]);

            Pattern pattern = Pattern.compile(args[0]);
            Matcher matcher = pattern.matcher(args[1]);

            while (matcher.find()) {
                System.out.println("Inicio em: " 
                    + matcher.start() 
                    + " Término em: "
                    + matcher.end()
                    + " - Encontrato: " 
                    + matcher.group());
            }
        }
    }
}

Quando o método find() retornar true, você pode usar o método start() para dizer onde o trecho que casa com o padrão se inicia, e o método end() para dizer onde ele termina. Novamente, o start() encara o inicio da string como 0, e o método end() encara o inicio da string como 1. O método group() retorna exatamente que trecho da expressão de entrada casou com o padrão, durante a execução do método find().

Obs.: Use o programa acima e padrões que usem quantificadores como * e ?, e avalie os resultados.

Casamento de padrões e a classe Scanner


Você pode usar a classe Scanner para ler a entrada de dados e buscar por determinado padrão usando o método Scanner.findInLine(). A classe Scanner não possui tantas funcionalidades quanto a classe Matcher, mas você pode usá-la para contar quantas instâncias de determinado padrão sua entrada de dados possui.

import java.util.regex.*;
import java.util.*;

public class RegexMatcher {
    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println (
                "Passe padrão como parametro");
        } else {
            System.out.println(
                "Padrao: " 
                + args[0]);

            System.out.print("input: ");
            System.out.flush();
            try {
                Scanner scanner = 
                    new Scanner(System.in);
                String token = null;
                do {
                    token = scanner.findInLine(args[0]);
                    System.out.println(
                        "encontrado: " 
                        + token);
                } while (token != null);
            } catch (Exception ex){
                ex.printStackTrace();
            }
        }
    }
}

Note que ao final da string que vc digitar como entrada, seu programa exibirá o null como token.



Creative Commons License
Esta obra está licenciada sob uma Licença Creative Commons.
Comentários
0 Comentários

0 comments:

Postar um comentário

Regras são chatas, mas...

- Seu comentário precisa ter relação com o assunto do post;
- Em hipótese alguma faça propaganda de outros blogs ou sites;
- Não inclua links desnecessários no conteúdo do seu comentário;
- Se quiser deixar sua URL, comente usando a opção OpenID;
- CAIXA ALTA, miguxês ou erros de ortografia não serão tolerados;
- Ofensas pessoais, ameaças e xingamentos não são permitidos;