DEV Community

Cover image for O que são Lookahead e Lookbehind em Expressões regulares
Lucas
Lucas

Posted on • Updated on

O que são Lookahead e Lookbehind em Expressões regulares


Introdução

Neste post estarei falando sobre uma possibilidade das expressões regulares que aprendi já tem alguns dias, mas me pareceu bastante interessante o conhecimento, então resolvi compartilhar.

Sendo mais específico, abordarei sobre essas 2 palavras do título, que a primeira vista podem soar meio estranhas e portanto, aqui vai uma tradução inicial sobre elas que talvez te ajudem a entender melhor mais a seguir:

  • Lookahead = Olhe para frente.
  • Lookbehind = Olhe para trás.

Bom, para um melhor aproveitamento sobre a leitura a seguir recomendo que já tenha noções básicas sobre as expressões regulares, mas caso não tenha ouvido falar sobre, não se preocupe, deixarei uma definição do assunto e um link de um post (ainda nesse site) que achei uma abordagem legal para se ler:

Expressões Regulares, ou regex, são sequências de caracteres que representam padrões utilizados para identificar caracteres literais em outros dados. As expressões regulares são extremamente úteis em várias situações, como validar formulários, extrair, filtrar e analisar dados, substituir e formatar textos, entre outras aplicações. Com a ajuda das regex, é possível verificar se determinados dados seguem um padrão específico, o que as torna uma ferramenta poderosa no processamento e manipulação de informações.

Post introdutório sobre as regex: Expressões Regulares I - o básico

O que são os lookarounds e para que são úteis

Como você já deve saber, podemos verificar padrões com o básico sobre as regex, mas nem sempre vamos conseguir fazer buscas da maneira mais eficiente se limitando ao básico. Imagine uma cena em que você precisa criar uma regex para receber apenas as quantidades de quilos sobre strings que possuem o seguinte formato:

'2kg - R$45,00 4kg - R$122,00 ...'

A primeira vista você pode estar pensando em usar a seguinte regex:

  • /\d+kg/g

Nessa regex, você estaria selecionando os caracteres que seguem o padrão de: 1 ou mais números que possuem a seguir os caracteres 'kg'. E sim, ela até funcionaria para retornar os quilos, mas não retornaria apenas a quantidade como desejamos acima já que retornaria também a unidade de medida, ou seja, enquanto desejamos apenas os números 2 e 4 da string, receberiamos 2kg e 4kg.
Então para resolver esse problema, poderiamos utilizar de um lookahead positivo, que ficaria da seguinte forma:

  • /\d+(?=kg)/g

Com a utilização desse lookahead, estariamos selecionando apenas os caracteres numéricos que possuírem os caracteres 'kg' após eles, mas sem os incluir na nossa busca. Em outras palavras, podemos encarar lookahead e lookbehind como criadores de condições para o que vamos selecionar, sem realizar o consumo dos caracteres utilizados na condição para a nossa correspondência, onde:

  • Lookahead: Verifica se os caracteres que estão após a nossa seleção possuem o padrão determinado.
  • Lookbehind: Verifica se os caracteres que estão antes da nossa seleção possuem o padrão determinado.

Bom, agora que você já tem uma ideia sobre para que servem, vamos explorar cada um deles:

Lookahead - olhe para frente

Como citado, com o Lookahead vamos criar uma condição para o que vem após o padrão que queremos consumir, para isso podemos utilizar de dois tipos de lookhead:

  • ?= -> chamado de lookahead positivo, retorna a seleção apenas se o que vier após ela seguir o padrão especificado.

  • ?! -> chamado de lookahead negativo, retorna a seleção apenas se o que vier após ela não seguir o padrão especificado.

Vamos ver um exemplo para cada um deles com JavaScript:

Lookahead positivo (?=)

Para esse exemplo vamos usar o lookahead positivo para retornar apenas o nome anterior das pessoas que possuírem o sobrenome 'Silva':

const nomes = [
    'Lucas Silva', 
    'Maria Silva Rodrigues', 
    'João Guilherme Silva', 
    'Pedro Oliveira',
    'Ana Julia Rodrigues'
    ]

const pattern = /.+(?=\sSilva)/
const primeirosNomes = nomes.reduce((acumulador, nome) => {
        const correspondente = nome.match(pattern);
        correspondente && acumulador.push(correspondente[0]);
        return acumulador
    }, [])

console.log(primeirosNomes); // [ 'Lucas', 'Maria', 'João Guilherme' ]
Enter fullscreen mode Exit fullscreen mode
  • Regex utilizada: /.+(?=\sSilva)/

  • Explicação da regex:

    • / -> São delimitadores, delimitam inicio e fim da regex.
    • .+ -> O ponto . representa qualquer caractere, e o quantificador + indica que o caractere anterior (qualquer caractere) deve ocorrer uma ou mais vezes.
    • (?=) -> Agrupa os caracteres para o lookahead positivo.
    • \sSilva -> Representa o padrão para o lookahead, onde \s é um metacaractere usado para representar um espaço e o resto é o sobrenome que estamos procurando.
  • Resultado da operação: [ 'Lucas', 'Maria', 'João Guilherme' ]

Lookahead negativo (?!)

Lembra da string usada no inicio desse artigo? Bom, caso não lembre vou refrescar sua memória:

'2kg - R$45,00 4kg - R$122,00 ...'

Para esse exemplo, irei complicar um pouco mais ela acrescentando uma representação para as gramas e trocando as vírgulas por pontos:

'2.50kg - R$45.00 4.35kg - R$122.00 5.60kg - R$122.00...'

Pronto! Mas... E se agora você quisesse pegar apenas os valores em reais? sem a quantidade de quilos ou a representação da moeda?
Para isso poderiamos utilizar do lookahead negativo, realizando uma lógica inversa ao que fizemos inicialmente:

const string = '2.50kg - R$45.00 4.35kg - R$122.00 5.60kg - R$136.00';
const pattern = /\d+\.\d{2}(?!kg)/g;
const valores = string.match(pattern);
console.log(valores); // [ '45.00', '122.00', '136.00' ]
Enter fullscreen mode Exit fullscreen mode
  • Regex utilizada: /\d+\.\d{2}(?!kg)/g

  • Explicação da regex:

    • \d+ -> Utiliza do metacaractere \d para representar um caractere numérico e do quantificador + para representar que ele pode aparecer uma ou mais vezes.
    • \. -> Representa o . (ponto) como caractere literal.
    • \d{2} -> Utiliza do quantificador {n} para insinuar que o caractere numérico deve ocorrer duas vezes.
    • (?!) -> Agrupa os caracteres para o lookahead negativo.
    • kg -> Representa o conjunto de caracteres que o nosso padrão especificado anteriormente não deve possuir após ele.
    • g -> Flag utilizada para fazer uma correspondência global, encontrando todas as ocorrências de um padrão em uma string.
  • Resultado da operação: [ '45.00', '122.00', '136.00' ]

Lookbehind - olhe para trás

Ao contrário do lookahead, com o lookbehind criamos uma condição que deve vir anteriormente ao padrão que queremos consumir, e assim como ele, também vamos ter dois tipos de uso:

  • ?<= -> chamado de lookbehind positivo, retorna a seleção apenas se o que vier antes dela possuir o padrão especificado.

  • ?<! -> chamado de lookbehind negativo, retorna a seleção apenas se o que vier antes dela não possuir o padrão especificado.

Também vou seguir os exemplos sobre eles com JavaScript, mas gostaria de deixar um aviso que alguns ambientes podem não possuir compatibilidade com o lookbehind ainda.

Lookbehind positivo (?<=)

Como sabemos, nem sempre vai existir apenas uma forma para realizar o que queremos, então para esse exemplo podemos utilizar do mesmo anterior, a diferença é que não olharemos para a frente dos caracteres, e sim para trás. Vou apresenta-lo a string novamente para você não precisar retornar ao exemplo anterior:

'2.50kg - R$45.00 4.35kg - R$122.00 5.60kg - R$122.00...'

Eaí, já pensou em uma forma para retornar apenas os valores em dinheiro com o lookbehind positivo? Vou te dar uma ideia de como seria possível:

const string = '2.50kg - R$45.00 4.35kg - R$122.00 5.60kg - R$136.00';
const pattern = /(?<=R\$)\d+\.\d{2}/g;
const valores = string.match(pattern);
console.log(valores); // [ '45.00', '122.00', '136.00' ]
Enter fullscreen mode Exit fullscreen mode
  • Regex utilizada: /(?<=R\$)\d+\.\d{2}/g

  • Explicação da regex:

    • (?<=) -> Agrupa os caracteres para o lookbehind positivo.
    • R\$ -> Representa os caracteres R$ de forma literal, nesse caso dentro do lookbehind positivo, verificamos se eles estão antes do padrão que queremos selecionar, caso não estejam o padrão não será selecionado.
    • \d+ -> Representa que terá a ocorrência de um ou mais caracteres numéricos.
    • \. -> Representa o . (ponto) como caractere literal.
    • \d{2} -> Indica que terá a ocorrência de dois caracteres numéricos.
    • g -> Flag para a correspondência global.
  • Resultado da operação: [ '45.00', '122.00', '136.00' ]

Lookbehind negativo (?<!)

Confesso que foi um pouco difícil achar um bom e simples exemplo pra utilizar do lookbehind negativo, já que muitos que eu pensava teria uma forma não tão interessante ou o uso ficava um pouco mais complexo do que deveria, mas felizmente achei um que acredito que será de fácil entendimento. Nesse exemplo você possuíra uma string com vários numeros positivos e negativos:

const numeros = '22 -32 43 -522 6 72 311 15 -13 ...';
Enter fullscreen mode Exit fullscreen mode

Enfim, como você pode utilizar do lookbehind negativo para retornar apenas os números positivos? Sinta-se a vontade para pensar em uma resposta antes de ver a resolução.

Como resposta pra isso você poderia utilizar da seguinte regex:

const numeros = '22 -32 43 -522 6 72 311 15 -13 ...';
const numerosPositivos = numeros.match(/(?<!-\d*)\d+/g);
console.log(numerosPositivos); // [ '22', '43', '6', '72', '311', '15' ]
Enter fullscreen mode Exit fullscreen mode
  • Regex utilizada: /(?<!-\d*)\d+/g

  • Explicação da regex:

    • (?<!) -> Agrupa os caracteres para o lookbehind negativo.
    • -\d* -> Indica que os números que queremos não devem ter o sinal - (negativo) anterior a ele, e utilizamos também de \d* para dizer que após o sinal de - vai ter um padrão de números, evitando que ocorra a seleção de unidades numéricas em números maiores que 9.
    • \d+ -> Representa que terá a ocorrência de um ou mais caracteres numéricos.
    • g -> Flag para a correspondência global.
  • Resultado da operação: [ '22', '43', '6', '72', '311', '15' ]

Sua vez

Agora que você já sabe como utilizar os termos citados acima, consegue utilizar de lookahead e de lookbehind para retornar apenas os nomes dos provedores dos emails abaixo?

const emails = ['john_doe@gmail.com', 'marilia022@yahoo.com.br', 'gustavo_amado@outlook.com']
Enter fullscreen mode Exit fullscreen mode

Vou deixar abaixo um código que você pode utilizar como base para a sua regex, e lembre-se, nesse desafio queremos apenas os nomes dos provedores, então você deve retornar uma lista com os seguintes valores: [ 'gmail', 'yahoo', 'outlook' ]

const emails = [ 
    'john_doe@gmail.com', 
    'marilia022@yahoo.com.br', 
    'gustavo_amado@outlook.com' 
]

const pattern = defina sua regex aqui;
const provedores = emails.reduce((acumulador, valor) => {
    const provedor = valor.match(pattern)[0];
    acumulador.push(provedor)
    return acumulador
}, [])

console.log(provedores);
Enter fullscreen mode Exit fullscreen mode

Top comments (0)