Quantificadores Gananciosos vs. Relutantes vs. Possessivos


357

Encontrei este excelente tutorial sobre expressões regulares e, embora compreenda intuitivamente o que os quantificadores "gananciosos", "relutantes" e "possessivos" fazem, parece haver um sério buraco no meu entendimento.

Especificamente, no exemplo a seguir:

Enter your regex: .*foo  // greedy quantifier
Enter input string to search: xfooxxxxxxfoo
I found the text "xfooxxxxxxfoo" starting at index 0 and ending at index 13.

Enter your regex: .*?foo  // reluctant quantifier
Enter input string to search: xfooxxxxxxfoo
I found the text "xfoo" starting at index 0 and ending at index 4.
I found the text "xxxxxxfoo" starting at index 4 and ending at index 13.

Enter your regex: .*+foo // possessive quantifier
Enter input string to search: xfooxxxxxxfoo
No match found.

A explicação menciona comer toda a cadeia de entrada, letras sido consumida , matcher recuando , a ocorrência mais à direita de "foo" foi regurgitado , etc.

Infelizmente, apesar das boas metáforas, ainda não entendo o que é comido por quem ... Você conhece outro tutorial que explica (concisa) como os mecanismos de expressões regulares funcionam?

Como alternativa, se alguém puder explicar de maneira um pouco diferente o seguinte parágrafo, isso seria muito apreciado:

O primeiro exemplo usa o quantificador ganancioso. * Para encontrar "qualquer coisa", zero ou mais vezes, seguido pelas letras "f" "o" "o". Como o quantificador é ganancioso, a parte. * Da expressão come primeiro toda a cadeia de entrada. Nesse ponto, a expressão geral não pode ser bem-sucedida, porque as três últimas letras ("f" "o" "o") já foram consumidas ( por quem? ). Portanto, o correspondente recua lentamente ( da direita para a esquerda? ) Uma letra de cada vez até que a ocorrência mais à direita de "foo" seja regurgitada ( o que isso significa? ), Momento em que a correspondência é bem-sucedida e a pesquisa termina.

O segundo exemplo, no entanto, é relutante, então começa consumindo primeiro ( por quem? ) "Nada". Como "foo" não aparece no início da string, é forçado a engolir ( quem engole?) A primeira letra (um "x"), que aciona a primeira correspondência em 0 e 4. Nosso equipamento de teste continua o processo até a sequência de entrada estar esgotada. Encontra outra partida em 4 e 13.

O terceiro exemplo falha ao encontrar uma correspondência porque o quantificador é possessivo. Nesse caso, toda a cadeia de entrada é consumida por. * +, ( Como? ), Não sobrando nada para satisfazer o "foo" no final da expressão. Use um quantificador possessivo para situações em que você deseja aproveitar tudo sem jamais recuar (o que significa recuar? ); superará o quantificador ganancioso equivalente nos casos em que a correspondência não for encontrada imediatamente.


22
Máximas quantificadores gosto *, +e ?são gananciosos. Mínimas quantificadores gosto *?, +?e ??são preguiçosos. Possessivos quantificadores gosto *+, ++e ?+são pegajoso.
tchrist

6
Esta pergunta foi adicionada às Perguntas frequentes sobre a expressão regular de estouro de pilha , em "Quantificadores> Mais sobre as diferenças ...".
precisa saber é o seguinte

De interesse: Os tutoriais de Java ™ - diferenças entre quantificadores gananciosos, relutantes e possessivos - role para baixo para ver a seção.
Guy Coder

Respostas:


495

Vou tentar.

Um quantificador ganancioso corresponde primeiro ao máximo. Então, .*corresponde à seqüência inteira. Em seguida, o correspondente tenta corresponder ao fseguinte, mas não há caracteres restantes. Por isso, "recua", fazendo com que o quantificador ganancioso corresponda a um caractere a menos (deixando o "o" no final da string inigualável). Isso ainda não corresponde ao fda expressão regular, portanto, retrocede mais uma etapa, fazendo com que o quantificador ganancioso corresponda a um caractere a menos novamente (deixando o "oo" no final da sequência inigualável). Isso ainda não coincide com o fno regex, por isso retrocede mais uma etapa (deixando o "foo" no final da string inigualável). Agora, o matcher finalmente combina com o fno regex,oosão combinados também. Sucesso!

Um quantificador relutante ou "não ganancioso" corresponde primeiro ao mínimo possível. Portanto, a .*partida não corresponde a nada, deixando a string inteira incomparável. Em seguida, o correspondente tenta corresponder ao fseguinte, mas a parte incomparável da sequência começa com "x" para que não funcione. Portanto, o correspondente recua, fazendo com que o quantificador não ganancioso corresponda a mais um caractere (agora corresponde ao "x", deixando "fooxxxxxxfoo" inigualável). Em seguida, ele tenta corresponder ao f, que é bem-sucedido, e o oe o próximo ono regex também. Sucesso!

No seu exemplo, ele inicia o processo novamente com a parte não correspondida restante da sequência "xxxxxxfoo", seguindo o mesmo processo.

Um quantificador possessivo é como o quantificador ganancioso, mas não retrocede. Então, começa com a .*correspondência de toda a cadeia, sem deixar nada incomparável. Então não há mais nada para combinar com o fno regex. Como o quantificador possessivo não volta atrás, a correspondência falha lá.


15
+1 boa resposta. Gostaria apenas de acrescentar: Go ler Dominando Expressões Regulares (3rd Edition)
Ridgerunner

@Anomie um pouco tarde, mas, na parte possessivo, eu acho que você quis dizer isso, começa com .*+ (note o "+")
RD

3
o que exatamente o quantificador possessivo faz então? se não combinar com isso? (Quero dizer, o que é o ponto de que, se você não pode ter caracteres depois dela)
relipse

4
Relipse @: você usaria em uma situação onde você sabe que o retorno não vai ajudar, provavelmente não com .*+isso corresponde a tudo. Por exemplo, se você tem um padrão [xyz]*foo, não há como retroceder os x, y e z correspondentes ao [xyz]*bit permitirem que o foobit a seguir corresponda, para que você possa acelerar as coisas tornando-o possessivo.
Anomie

4
@ moodboom, existem zero casos (fato matemático) em que quantificadores possessivos produzirão uma correspondência que não será produzida por quantificadores gananciosos simples. Há casos ocasionais em que eles não produzem correspondência, quando quantificadores gananciosos produzem uma correspondência. Para TODOS os outros casos (em que gananciosos e possessivos produzem os mesmos resultados), os quantificadores possessivos fornecem um ganho de desempenho.
Curinga

49

É apenas o resultado da minha prática visualizar a cena

Imagem visual


3
Só que acho que o último caso, possessivo, não deveria ter n passes - basta pegar a corda inteira de uma só vez.
trate bem seus mods

@phyzome Acho que está tudo bem agora?
SIslam

11
Obrigado pela explicação visual :)
Lars Moelleken

Em EXPRESSION .*?foo(), os [f] [o] [o]retângulos não deveriam estar amarelos no 5th pass?
tonix

11
@tonix yes! A coloração amarela precisa ser feita para a parte correspondente na expressão .*?fooe .*+foo.
SIslam

24

Eu nunca ouvi os termos exatos 'regurgitar' ou 'recuar' antes; a frase que os substituirá é "retroceder", mas "regurgitar" parece ser uma frase tão boa quanto qualquer "para o conteúdo que foi tentativamente aceito antes de retroceder o jogou fora".

O importante a ser percebido sobre a maioria dos mecanismos de regex é que eles estão voltando atrás : eles tentativamente aceitam uma correspondência potencial e parcial, enquanto tentam corresponder todo o conteúdo da regex. Se o regex não puder ser completamente correspondido na primeira tentativa, o mecanismo regex retornará em uma de suas correspondências. Ele vai testar a correspondência *, +, ?, alternância, ou {n,m}repetição de forma diferente, e tente novamente. (E sim, esse processo pode demorar muito.)

O primeiro exemplo usa o quantificador ganancioso. * Para encontrar "qualquer coisa", zero ou mais vezes, seguido pelas letras "f" "o" "o". Como o quantificador é ganancioso, a parte. * Da expressão come primeiro toda a cadeia de entrada. Nesse ponto, a expressão geral não pode ser bem-sucedida, porque as três últimas letras ("f" "o" "o") já foram consumidas ( por quem? ).

As últimas três letras, f, o, e oforam já consumidos pela primeira .*parte da regra. No entanto, o próximo elemento na regex,, fnão tem mais nada na cadeia de entrada. O mecanismo será forçado a voltar atrás em sua .*partida inicial e tentar combinar o personagem quase o último. (Pode ser inteligente e voltar aos três últimos, porque possui três termos literais, mas não conheço os detalhes da implementação nesse nível.)

Assim, o jogador recua lentamente ( da direita para a esquerda? ) Uma letra de cada vez até que a ocorrência mais à direita de "foo" seja regurgitada ( o que isso significa? ), Na qual

Isso significa fooque, provisoriamente, foi incluído na correspondência .*. Como essa tentativa falhou, o mecanismo regex tenta aceitar um caractere a menos .*. Se houvesse uma correspondência bem-sucedida antes da .*neste exemplo, o mecanismo provavelmente tentaria encurtar a .*correspondência (da direita para a esquerda, como você apontou, porque é um qualificador ganancioso) e se não pudesse corresponder todas as entradas, pode ser forçado a reavaliar o que havia correspondido antes do .*no meu exemplo hipotético.

aponte a correspondência e a pesquisa termina.

O segundo exemplo, no entanto, é relutante, então começa consumindo primeiro ( por quem? ) "Nada". Porque "foo"

O nada inicial é consumido .?*, o que consumirá a menor quantidade possível de qualquer coisa que permita que o restante da regex corresponda.

não aparece no início da corda, é forçado a engolir ( quem engole?) o

Novamente, ele .?*consome o primeiro caractere, após retornar atrás na falha inicial em corresponder a regex inteira com a menor correspondência possível. (Nesse caso, o mecanismo regex está estendendo a correspondência .*?da esquerda para a direita, porque .*?é relutante.)

primeira letra (um "x"), que aciona a primeira correspondência em 0 e 4. Nosso equipamento de teste continua o processo até que a string de entrada esteja esgotada. Encontra outra partida em 4 e 13.

O terceiro exemplo falha ao encontrar uma correspondência porque o quantificador é possessivo. Nesse caso, toda a cadeia de entrada é consumida por. * +, ( Como? )

A .*+consumirá o máximo possível e não retornará para encontrar novas correspondências quando a regex como um todo falhar em encontrar uma correspondência. Porque a forma possessiva não executa retrocesso, você provavelmente não vai ver muitos usos com .*+, mas sim com classes de personagens ou restrições semelhantes: account: [[:digit:]]*+ phone: [[:digit:]]*+.

Isso pode acelerar drasticamente a correspondência de regex, porque você está dizendo ao mecanismo de regex que ele nunca deve voltar atrás em correspondências em potencial se uma entrada não corresponder. (Se você tivesse que escrever todo o código correspondente à mão, seria semelhante a nunca usar putc(3)para 'empurrar' um caractere de entrada. Seria muito semelhante ao código ingênuo que se poderia escrever em uma primeira tentativa. Exceto os mecanismos regex são muito melhor do que um único caractere de retrocesso, eles podem retroceder todo o retorno para zero e tentar novamente. :)

Mas, além das possíveis acelerações, isso também permite escrever regexs que correspondem exatamente ao que você precisa. Estou tendo problemas para encontrar um exemplo fácil :), mas escrever um regex usando quantificadores possessivos vs gananciosos pode fornecer correspondências diferentes e um ou outro pode ser mais apropriado.

não sobrando nada para satisfazer o "foo" no final da expressão. Use um quantificador possessivo para situações em que você deseja aproveitar tudo sem jamais recuar (o que significa recuar? ); vai superar

"Recuar" nesse contexto significa "recuar" - descartando uma correspondência parcial tentativa para tentar outra correspondência parcial, que pode ou não ser bem-sucedida.

o quantificador ganancioso equivalente nos casos em que a correspondência não é encontrada imediatamente.


2
Suspeito que nunca haja um caso em que um quantificador possessivo corresponda a algo que um quantificador ganancioso não corresponderá. Acredito que o seguinte prova isso: um quantificador ganancioso sempre corresponde o máximo possível e depois recua se não conseguir encontrar uma correspondência. Um quantificador possessivo corresponde o máximo possível e sai se não conseguir encontrar uma correspondência. Portanto, pode haver algo que um quantificador ganancioso corresponda ao que um quantificador possessivo não fará, mas não o contrário, porque ambos pesquisam a "árvore" na mesma sequência, o quantificador possessivo simplesmente desiste mais facilmente. ;)
Curinga

2
Confirmado: "É para isso que servem o agrupamento atômico e os quantificadores possessivos: eficiência, impedindo o retorno." from regular-expressions.info Portanto, a declaração desta resposta "Mas mais do que possíveis acelerações, isso também permite escrever regexs que correspondem exatamente ao que você precisa". na verdade não é bem preciso.
Curinga

11
@Wildcard, obrigado pelos comentários; isso pode explicar por que tive problemas em criar um exemplo. Ele Ele.
sarnold

19

http://swtch.com/~rsc/regexp/regexp1.html

Não sei se essa é a melhor explicação na internet, mas ela é razoavelmente bem escrita e adequadamente detalhada, e continuo voltando a ela. Você pode querer dar uma olhada.

Se você deseja um nível mais alto (explicação menos detalhada), para expressões regulares simples como a que você está visualizando, um mecanismo de expressão regular funciona retornando. Essencialmente, ele escolhe ("come") uma seção da string e tenta combinar a expressão regular com essa seção. Se combinar, ótimo. Caso contrário, o mecanismo altera sua escolha da seção da string e tenta corresponder o regexp a essa seção, e assim por diante, até tentar todas as opções possíveis.

Esse processo é usado recursivamente: na tentativa de combinar uma string com uma determinada expressão regular, o mecanismo dividirá a expressão regular em partes e aplicará o algoritmo a cada parte individualmente.

A diferença entre quantificadores gananciosos, relutantes e possessivos entra quando o mecanismo faz suas escolhas de qual parte da string tentar combinar e como modificar essa opção se não funcionar da primeira vez. As regras são as seguintes:

  • Um quantificador ganancioso diz ao mecanismo para começar com a sequência inteira (ou pelo menos, tudo isso que ainda não foi correspondido por partes anteriores da expressão regular) e verificar se corresponde à regexp. Se sim, ótimo; o mecanismo pode continuar com o restante da regexp. Caso contrário, ele tenta novamente, mas aparando um caractere (o último) da seção da string a ser verificada. Se isso não funcionar, ele remove outro caractere, etc. Portanto, um quantificador ganancioso verifica possíveis correspondências na ordem do maior para o menor.

  • Um quantificador relutante diz ao mecanismo para começar com o menor pedaço possível da corda. Se corresponder, o mecanismo pode continuar; caso contrário, ele adiciona um caractere à seção da string que está sendo verificada e tenta isso, e assim sucessivamente até encontrar uma correspondência ou a string inteira ter sido usada. Portanto, um quantificador relutante verifica possíveis correspondências na ordem do menor para o maior.

  • Um quantificador possessivo é como um quantificador ganancioso na primeira tentativa: ele diz ao mecanismo para começar verificando toda a string. A diferença é que, se não funcionar, o quantificador possessivo informa que a correspondência falhou naquele momento. O mecanismo não altera a seção da string que está sendo examinada e não faz mais tentativas.

É por isso que a correspondência possessiva do quantificador falha no seu exemplo: a .*+verificação é feita com relação a toda a cadeia, à qual corresponde, mas o mecanismo continua a procurar caracteres adicionais foodepois disso - mas é claro que não os encontra, porque você já está no final da string. Se fosse um quantificador ganancioso, ele retornaria e tentaria fazer a .*única correspondência até o penúltimo caractere, depois até o terceiro para o último caractere e, em seguida, até o quarto para o último caractere, o que é bem-sucedido porque só então é há uma fooesquerda depois de .*"comer" a parte anterior da corda.


11
Essa é uma excelente fonte. Eu amo diagramas de máquinas de estado. :)
Regex Rookie

@ Regex Rookie: que bom que você gostou :) Depois de examinar o site, acho que devo deixar claro que seu objetivo é promover uma implementação alternativa de um mecanismo de expressão regular. O algoritmo de retrocesso I (parcialmente) e outras respostas descrevem é o caminho mais lento ; é um algoritmo completamente separado da idéia NFA / DFA descrita na página da web. O retorno é apenas mais fácil de entender, e é por isso que os regexps são normalmente explicados aos iniciantes.
David Z #

@ David Zaslavsky: Boa explicação. Seus comentários entre parênteses em "Um quantificador ganancioso diz ao mecanismo para iniciar com a sequência inteira (ou pelo menos, tudo isso que ainda não foi correspondido por partes anteriores da expressão regular)" é importante. Eles se aplicam também a quantificadores relutantes e possessivos. Isso torna sua explicação compatível com o que acontece quando alteramos nossos padrões de exemplo de (". * Foo"; ". *? Foo"; e ". * + Foo") para ("foo. *"; "Foo. *? "; e" foo. * + ").
John Bentley

Na verdade, xfooxxxxxxfoo corresponde. * Foo no normal (significado da ciência da computação) da expressão regular. O NFA seria um estado em que se entrelaça com qualquer caractere e então pode pular para o chão. O DFA seria uma tradução direta desse NFA. Isso pode ser feito em 8 estados.
usar o seguinte comando

@ JimThio sim, porque esse não é um quantificador possessivo.
David Z

13

Aqui está minha opinião usando as posições Cell e Index (veja o diagrama aqui para distinguir uma célula de um índice).

Ganancioso - Combine o máximo possível com o quantificador ganancioso e toda a regex. Se não houver correspondência, volte no quantificador ganancioso.

String de entrada: xfooxxxxxxfoo
Regex:. * Foo

O Regex acima tem duas partes:
(i) '. *' E
(ii) 'foo'

Cada uma das etapas abaixo analisará as duas partes. Comentários adicionais para uma correspondência com 'Aprovado' ou 'Reprovado' são explicados entre chaves.

Etapa 1:
(i). * = Xfooxxxxxxfoo - PASS ('. *' É um quantificador ganancioso e usará toda a String de entrada)
(ii) foo = Nenhum caractere a ser correspondido após o índice 13 -
Falha na correspondência.

Etapa 2:
(i). * = Xfooxxxxxxfo - PASS (Retroceder no quantificador ganancioso '. *')
(Ii) foo = o - FAIL A
correspondência falhou.

Etapa 3:
(i). * = Xfooxxxxxxf - PASS (Retrocedendo no quantificador ganancioso '. *')
(Ii) foo = oo - FAIL
correspondência falhou.

Etapa 4:
(i). * = Xfooxxxxxx - PASS (Retroceder no quantificador ganancioso '. *')
(Ii) foo = foo - PASS
Report MATCH

Resultado: 1 correspondência (s)
Encontrei o texto "xfooxxxxxxfoo" começando no índice 0 e terminando no índice 13.

Relutante - corresponda o mínimo possível ao quantificador relutante e corresponda a toda a regex. se não houver correspondência, adicione caracteres ao quantificador relutante.

String de entrada: xfooxxxxxxfoo
Regex:. *? Foo

O regex acima tem duas partes:
(i) '. *?' e
(ii) 'foo'

Etapa 1:.
*? = '' (em branco) - PASS (Corresponde o mínimo possível ao quantificador relutante '. *?'. O índice 0 com '' é uma correspondência.)
foo = xfo - FAIL (célula 0,1,2 - isto é, índice entre 0 e 3) A
correspondência falhou.

Etapa 2:.
*? = x - PASS (Adicione caracteres ao quantificador relutante '. *?'. A célula 0 com 'x' é uma correspondência.)
foo = foo - PASS
Report MATCH

Etapa 3:.
*? = '' (em branco) - PASS (Corresponde o mínimo possível ao quantificador relutante '. *?'. O índice 4 com '' é uma correspondência.)
foo = xxx - FAIL (célula 4,5,6 - ie índice entre 4 e 7) A
correspondência falhou.

Etapa 4:.
*? = x - PASS (Adicione caracteres ao quantificador relutante '. *?'. Célula 4.)
foo = xxx - FAIL (Célula 5,6,7 - ie índice entre 5 e 8)
correspondência falhou.

Etapa 5:.
*? = xx - PASS (Adicione caracteres ao quantificador relutante '. *?'. Célula 4 a 5.)
foo = xxx - FAIL (Célula 6,7,8 - ou seja, índice entre 6 e 9)
correspondência falhou.

Etapa 6:.
*? = xxx - PASS (Adicione caracteres ao quantificador relutante '. *?'. Célula 4 a 6.)
foo = xxx - FAIL (Célula 7,8,9 - ou seja, índice entre 7 e 10)
correspondência falhou.

Etapa 7:.
*? = xxxx - PASS (Adicione caracteres ao quantificador relutante '. *?'. Célula 4 a 7.)
foo = xxf - FAIL (Célula 8,9,10 - ou seja, índice entre 8 e 11)
correspondência falhou.

Etapa 8:.
*? = xxxxx - PASS (Adicione caracteres ao quantificador relutante '. *?'. Célula 4 a 8.)
foo = xfo - FAIL (Célula 9,10,11 - ou seja, índice entre 9 e 12)
correspondência falhou.

Etapa 9:.
*? = xxxxxx - PASS (Adicione caracteres ao quantificador relutante '. *?'. Célula 4 a 9.)
foo = foo - PASS (Célula 10,11,12 - ou seja, índice entre 10 e 13)
FICHA DE JOGO

Etapa 10:.
*? = '' (em branco) - PASS (Corresponde o mínimo possível ao quantificador relutante '. *?'. O índice 13 está em branco.)
foo = Nenhum caractere a ser correspondido - FAIL (Não há nada após o índice 13 a corresponder)
Jogo falhou.

Resultado: 2 correspondências
Encontrei o texto "xfoo" começando no índice 0 e terminando no índice 4.
Encontrei o texto "xxxxxxfoo" começando no índice 4 e terminando no índice 13.

Possessivo - Corresponde o máximo possível ao quantificador possessivo e corresponda a toda a expressão regular. NÃO volte atrás.

String de entrada: xfooxxxxxxfoo
Regex: * + Foo

O regex acima tem duas partes: '. * +' E 'foo'.

Etapa 1:.
* + = Xfooxxxxxxfoo - PASS (Corresponde o máximo possível ao quantificador possessivo '. *')
Foo = Nenhum caractere a ser correspondido - FAIL (Nada a corresponder após o índice 13) A
correspondência falhou.

Nota: O retorno não é permitido.

Resultado: 0 resultado (s)


1

Ganancioso: "corresponde à maior seqüência possível de caracteres"

Relutante: "corresponde à menor seqüência possível de caracteres"

Possessivo: Isso é um pouco estranho, pois NÃO (ao contrário de ganancioso e relutante) tenta encontrar uma correspondência para todo o regex.

A propósito: Nenhuma implementação do correspondente do padrão regex jamais usará o retorno. Todos os padrões de vida real são extremamente rápidos - quase independentes da complexidade da expressão regular!


Até onde eu sei, a maioria das implementações de uso geral estão agora tão cheias de recursos que ficou impossível não usar o retorno. Portanto, em teoria, eles devem ser extremamente (exponencialmente) lentos para alguns casos. Mas para a maioria desses casos, há otimizações especiais integradas no correspondente de padrões.
Robert

0

A quantificação gananciosa envolve a correspondência de padrões usando todos os caracteres não validados restantes de uma sequência durante uma iteração. Caracteres não validados iniciam na sequência ativa . Toda vez que uma partida não ocorre, o personagem no final é colocado em quarentena e a verificação é realizada novamente.

Quando apenas as condições iniciais do padrão regex são satisfeitas pela sequência ativa, é feita uma tentativa de validar as condições restantes contra a quarentena. Se essa validação for bem-sucedida, os caracteres correspondentes na quarentena serão validados e os caracteres não correspondentes residuais permanecerão sem validação e serão usados ​​quando o processo começar novamente na próxima iteração.

O fluxo de caracteres é da sequência ativa para a quarentena. O comportamento resultante é que o máximo possível da sequência original seja incluído em uma correspondência.

A quantificação relutante é basicamente a mesma que a qualificação gananciosa, exceto que o fluxo de caracteres é o oposto - ou seja, eles começam na quarentena e fluem para a sequência ativa . O comportamento resultante é que o mínimo da sequência original seja incluído em uma correspondência possível.

A quantificação possessiva não possui quarentena e inclui tudo em uma sequência ativa fixa .

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.