Por Lucas Duarte
Parte 1 - Motivações e Conceitos Básicos
Por que expressões regulares?
Como profissionais da computação, muitos de nós, um dia, precisamos (ou precisaremos!) fazer buscas de textos que apresentam padrões complexos, dentro de uma fonte de dados potencialmente grande, de forma automatizada.
Seja para limpar dados antes da aplicação de algoritmos de aprendizado de máquina, seja para alterar formatos de dados para que fiquem compatíveis ou utilizáveis por consumidores que exigem formatos específicos, as expressões regulares podem ser a ferramenta mais útil à disposição.
Este post apresenta alguns conceitos básicos e um exemplo de como o uso de expressões regulares facilita a resolução de um problema exemplo. Em um próximo post, iremos mais a fundo nos diferentes tipos de expressões disponíveis. Fique atento!
Resolvendo um problema sem expressões regulares
Como ilustração e para termos uma base de comparação, vamos resolver um problema de busca por padrões de texto sem a utilização de expressões regulares em um primeiro momento e analisaremos os procedimentos necessários. Mais adiante, empregaremos as expressões regulares para a resolução do mesmo problema.
Considere o endereço mostrado abaixo um exemplo de texto de entrada, o qual iremos vasculhar em busca de um determinado padrão de caracteres:
A figura acima mostra parte do conteúdo do website da Daitan.
Vamos supor que queiramos encontrar o CEP dentro do texto do endereço destacado na figura, desconsiderando o traço e retendo apenas os números, da seguinte forma:
A string ilustrada acima corresponde ao texto extraído do site (por exemplo, por meio de parsing do código HTML) e o padrão que esperamos encontrar em alguma posição desta cadeia de caracteres pode ser expressado da seguinte forma:
<span style="font-weight: 400;"><i><span data-rich-text-format-boundary="true">dígito </span></i><i> </i><i>dígito </i><i> </i><i>dígito </i><i> </i><i>dígito </i><i> </i><i>dígito </i><i> </i><span style="text-decoration: underline;"><i>hífen</i></span> <i> </i><i>dígito </i><i> </i><i>dígito </i><i> </i><i>dígito</i></span>
Vamos prosseguir ao seguinte código Java, que faz a varredura da string de entrada, caractere a caractere, em busca do padrão desejado:
A classe CepFinder traz o método findCep, que recebe a string de entrada e percorre o texto em busca do padrão desejado.
A ideia é iterar de caractere em caractere, buscando por um dígito — um candidato a primeiro dígito do CEP. Caso ainda existam 8 dígitos à frente na string, e caso estes 8 dígitos apresentem o padrão abaixo, encontramos o CEP desejado:
<i><span style="font-weight: 400;">dígito dígito dígito dígito </span></i><span style="text-decoration: underline;"><i><span style="font-weight: 400;">hífen</span></i></span><i><span style="font-weight: 400;"> dígito dígito dígito</span></i>
Esta solução, embora perfeitamente funcional, pode se tornar bastante complexa caso tenhamos padrões complicados para buscar.
Apliquemos agora uma expressão regular para encontrar o código CEP desejado. Antes, porém, vejamos algumas breves definições.
Algumas definições
Em posse de um padrão de caracteres que desejamos encontrar (no nosso caso, um código CEP) podemos usar textos especiais que descrevem o padrão que desejamos encontrar — as expressões regulares.
Embora possamos descrever o padrão desejado como
<i><span style="font-weight: 400;">dígito dígito dígito dígito </span></i><span style="text-decoration: underline;"><i><span style="font-weight: 400;">hífen</span></i></span><i><span style="font-weight: 400;"> dígito dígito dígito</span></i>
também podemos usar a seguinte expressão regular: \d\d\d\d\d-\d\d\d.
O símbolo \d corresponde a dígitos, ou seja, faz match com qualquer número entre 0 e 9. Com esta expressão, continuamos descrevendo o padrão desejado como “cinco dígitos, um hífen e três dígitos”.
Podemos inclusive representar as quantidades desejadas ao invés de repetir o símbolo \d:
\d{5}-\d{3}
E usando os parênteses, conseguimos agrupar porções do match (o texto encontrado):
(\d{5})-(\d{3})
Desta forma, poderemos nos referir aos primeiros 5 dígitos do texto encontrado como grupo 1 ou $1 e, da mesma forma aos 3 dígitos finais do texto encontrado como grupo 2 ou $2.
Passemos agora à resolução do problema inicial com o uso das definições acima.
Resolvendo o problema com expressões regulares
No código acima, temos exatamente a mesma string vazia inicial (linha 09), a verificação final de seu conteúdo com possível lançamento de exceção (linhas 17-18) e o retorno do CEP encontrado (linha 20).
Na linha 12 começamos a fazer o uso da expressão regular que definimos na seção anterior:
(\d{5})-(\d{3})
A classe Pattern provê um método com escopo de classe chamado compile, que recebe nossa expressão regular em formato de objeto String.
Repare nas contrabarras duplas na string fornecida como argumento ao método compile. As barras invertidas precisam ser precedidas pelo caractere de escape (outra barra invertida) nas strings em Java. Para facilitar a visualização da expressão regular utilizada, a linha 11 a traz em forma de comentário.
O método compile funciona como uma factory, e retorna uma instância da classe Pattern. Em posse do objeto retornado, podemos acionar o método matcher, que recebe o texto no qual queremos buscar o padrão e retorna um objeto do tipo Matcher.
Ao chamar o método find do objeto Matcher (linha 14) disparamos a busca pelo padrão descrito pela expressão regular. Este método retorna true caso um match seja encontrado e, como agrupamos os dígitos com parênteses em nossa expressão regular, podemos obter os 5 primeiros dígitos do CEP (grupo 1) e os três últimos dígitos (grupo 2) por meio do método group (linha 15).
Considerações finais
Percebe-se pela comparação dos dois códigos mostrados que as expressões regulares têm o poder de simplificar lógicas bastante complicadas, sobretudo quando o padrão a ser buscado torna-se mais complexo. Elas se mostram bastante úteis em parsing de textos, por exemplo.
Existem ferramentas gratuitas que suportam buscas com expressões regulares e podem ser usadas para testes e até mesmo aprendizado — uma delas é o Microsoft Visual Studio Code. Ao aplicar expressões regulares em programas sendo desenvolvidos é interessante, contudo, verificar se a expressão regular em questão tem realmente o comportamento esperado na linguagem de programação em uso, pois existem diferentes flavors (variações) de expressões regulares.
Em casos de uso em larga escala, com textos de entrada e expressões regulares crescentes em tamanho e complexidade, é bastante importante dar atenção ao crescimento do tempo de execução. Embora este ponto fuja do escopo deste artigo, podem existir casos extremos em que o tempo de execução passa a crescer exponencialmente com a complexidade dos textos de entrada e das expressões regulares utilizadas.
Em um próximo artigo abordaremos o uso das expressões regulares com várias definições e exemplos ilustrativos.
Lucas é especialista em desenvolvimento de software na Daitan. Formado em engenharia de computação pela Universidade Federal de Itajubá, possui experiência em automação e paralelização de procedimentos e tarefas envolvendo controle e comunicação com equipamentos remotos, extração e geração de dados e integração com outros sistemas de software.