O Que É Regularidade - O que é regularidade de uma sequência?
O que é regularidade de uma sequência?

Entendendo regularidade na prática

A regularidade é um conceito que aparece em várias áreas — teoria dos autómatos, processamento de texto, matemática — mas o mais comum no dia a dia é quando falamos de expressões regulares e da classe de linguagens regulares. A ideia central é simples: um padrão que pode ser reconhecido por um autômato finito, ou seja, algo que você consegue descrever com regras fixas e repetitivas, sem precisar "lembrar" de quantas coisas aconteceram anteriormente. Eu já vi muita gente confundir regularidade com complexidade. Na verdade, linguagens regulares são justamente as mais limitadas do ponto de vista computacional. Elas não conseguem contar, não conseguem verificar se parênteses estão balanceados, não conseguem fazer correspondência de estruturas aninhadas. O teste é direto: se precisa de uma memória infinita para validar, não é regular.

o que é regularidade

Do ponto de vista formal, regularidade se define por três operações básicas: união, concatenação e estrela de Kleene. Qualquer linguagem que possa ser construída usando apenas essas três operações a partir de conjuntos finitos de caracteres é uma linguagem regular. Exemplos clássicos incluem strings que terminam em "01", strings com número par de "a"s, ou qualquer sequência que siga um padrão fixo repetível. Na prática de programação, o equivalente são as expressões regulares usadas em grep, Python, JavaScript, sed, e por aí vai. Um padrão como ^[a-z]+\@[a-z]+\.[a-z]{2,3}$ é regular — ele verifica se um texto tem o formato de um e-mail simples, sem recursão ou memória de estados anteriores.

Uma coisa que poucos entendem de cara: expressões regulares modernas que a maioria das bibliotecas oferece vão além do que a teoria chama de "regular". Backreferences, lookahead, lookup, gramáticas dependentes de contexto — nada disso é realmente regular segundo a definição formal. É uma extensão pragmática que ganhou o mesmo nome por convenção histórica. Esse detalhe importa porque te dá poder, mas também te expõe a comportamentos inesperados. Eu tive um problema específico há alguns anos num sistema de validação de dados. Estávamos usando uma regex com backreference para validar CPF: algo do tipo que captura os três primeiros blocos de dígitos e depois usa \1 para comparar. O problema era que a regex funcionava perfeitamente nos testes unitários, mas no produção, com milhões de registros, ela causava travamento no serviço. O mecanismo de matching do motor de regex entrava em backtracking exponencial quando o input era maliciosamente crafted — uma sequência de dígitos quase válida que forçava o autômato a explorar bilhões de caminhos possíveis antes de rejeitar.

A solução foi simples, mas demorei para perceber: substituir a regex por um algoritmo procedural em Python que valida CPF digitando dígito por dígito, calculando os módulos 11 e comparando. Levou dois dias implementar e testar, e eliminou completamente o problema de performance. regex era overkill para aquilo.

Como identificar se algo é regular ou não

O teste mais confiável é o lema do bombeamento para linguagens regulares. Se uma linguagem não consegue satisfazer as condições do lema — basicamente, se existe um comprimento arbitrário além do qual você sempre consegue "bombeartrecho" da string mantendo-a dentro da linguagem — então a linguagem não é regular. Um exemplo clássico: a linguagem das strings com número igual de "a"s e "b"s não é regular. Por quê? Porque para validar isso, você precisa contar quantos "a"s apareceu e depois verificar se o número de "b"s é exatamente o mesmo. Um autômato finito tem memória limitada, então ele não consegue fazer essa contagem para qualquer tamanho de entrada.

Outro exemplo prático: se você precisa validar estruturas aninhadas — como tags HTML, parênteses balanceados, ou JSON — expressões regulares puras não vão funcionar. A hierarquia de Chomsky coloca essas linguagens em níveis mais altos (livres de contexto ou acima). Usar regex para isso é possível com extensões, mas vai contra o propósito e geralmente gera código frágil e difícil de manter.

👉 Clique no botão abaixo para saber mais sobre o assunto!

Quando usar regex e quando fugir

Expressões regulares são ideais para três tipos de problema: captura de padrões simples (datas, IPs, tokens), substituição textual, e filtragem de linhas em logs ou arquivos de texto. Para tudo isso, uma regex bem escrita roda em tempo constante e consome memória mínima. Fuja de regex quando o padrão exige: memória de estado ilimitado, contagem precisa, correspondência de estruturas aninhadas, ou validação de integridade estrutural complexa. Nesses casos, escreva um parser pequeno ou use uma biblioteca de parsing dedicada. A diferença de tempo entre uma regex mal escolhida e um parser adequado pode ser de minutos para segundos, e a legibilidade do código também melhora drasticamente.

Outro erro comum é achar que regex é a solução para tudo. Já vi pipeline de ETL inteiro dependente de regex para limpeza de dados, quando uma simples função de string ou uma regra de negócio bem definida resolveria em uma linha de código legível. Regex é uma ferramenta, não um destino.

Dicas práticas para escrever regex eficiente

A principal é: sequestradores de performance existem e são silenciosos. Lookaheads aninhados, alternâncias com sobreposição de capítulos, e grupos de captura em loops são os culpados mais comuns. Se sua regex leva mais de 10ms para processar uma string de 1KB, algo está errado. Segundo: use flags de otimização quando disponíveis. Em Python, o módulo regex (diferente de re) oferece modos de validação mais rápidos e detecção automática de backtracking catastrófico. Em Perl e PCRE, o modificador (?D) e o modo JIT podem acelerar matchings repetitivos em ordens de grandeza.

Terceiro: prefira expressões atômicas ou possessivas quando possível. Grupos possessivos como (?>...) impedem o backtracking após uma correspondência bem-sucedida, o que elimina categorias inteiras de problemas de performance. A desvantagem é que você perde a capacidade de "desfazer" escolhas, então só use quando tiver certeza de que o padrão está correto.

Limitações que todo mundo esquece

Expressões regulares não são determinísticas por natureza. Um mesmo input pode ser matcheado de múltiplas formas dependendo da ordem das alternâncias e da presença de grupos capturadores. Isso causa bugs sutis em sistemas que dependem de resultados consistentes — como um validador de formulário que aceita um input em uma linguagem mas rejeita em outra porque os motores de regex implementam diferentes semânticas de lookahead. Além disso, a portabilidade entre linguagens é pior do que muitos imaginam. O que funciona em Python não necessariamente funciona em JavaScript, e vice-versa. Lookbehind de largura variável, por exemplo, é suportado em Python e PCRE mas não em JavaScript até versões recentes. Se seu código precisa rodar em múltiplos ambientes, testar em todos eles não é opcional.

Por fim, regex tende a se tornar ilegível rapidamente. Um padrão de 80 caracteres com cinco grupos de captura e dois lookaheads é dificilmente compreensível por alguém que não escreveu. Documentar a regex com comentários inline ou extrair para funções nomeadas é uma prática que evita dor de cabeça futura. Código é para ser lido por humanos primeiro e por máquinas depois.