Eu estava lendo uma pergunta sobre como obter dados entre chaves duplas ( esta pergunta ), e então alguém mencionou grupos de equilíbrio. Ainda não tenho certeza do que são e como usá-los.
Eu li a Definição do Grupo de Balanceamento , mas a explicação é difícil de acompanhar e ainda estou bastante confuso com as perguntas que mencionei.
Alguém poderia simplesmente explicar o que são grupos de equilíbrio e como eles são úteis?
c#
.net
regex
balancing-groups
It'sNotALie.
fonte
fonte
Respostas:
Até onde eu sei, os grupos de balanceamento são exclusivos do tipo regex do .NET.
À parte: grupos repetidos
Primeiro, você precisa saber que o .NET é (novamente, até onde eu sei) o único tipo de regex que permite acessar várias capturas de um único grupo de captura (não em referências anteriores, mas após a conclusão da correspondência).
Para ilustrar isso com um exemplo, considere o padrão
e a corda
"abcd"
.em todos os outros sabores de regex, a captura de grupo
1
simplesmente produzirá um resultado:d
(observe, a correspondência completa seráabcd
como o esperado). Isso ocorre porque cada novo uso do grupo de captura sobrescreve a captura anterior.O .NET, por outro lado, lembra de todos eles. E faz isso em uma pilha. Depois de combinar o regex acima, como
Vai descobrir que
É um
CaptureCollection
cujos elementos correspondem às quatro capturasonde o número é o índice no
CaptureCollection
. Então, basicamente, toda vez que o grupo é usado novamente, uma nova captura é colocada na pilha.Fica mais interessante se estivermos usando grupos de captura nomeados. Como o .NET permite o uso repetido do mesmo nome, poderíamos escrever um regex como
para capturar duas palavras no mesmo grupo. Novamente, toda vez que um grupo com um determinado nome é encontrado, uma captura é colocada em sua pilha. Então, aplicando esta regex à entrada
"foo bar"
e inspecionandoencontramos duas capturas
Isso nos permite até mesmo colocar coisas em uma única pilha de diferentes partes da expressão. Mas, ainda assim, este é apenas o recurso do .NET de ser capaz de rastrear várias capturas que estão listadas aqui
CaptureCollection
. Mas eu disse, essa coleção é uma pilha . Então, podemos tirar coisas dele?Entrar: Grupos de Balanceamento
Acontece que podemos. Se usarmos um grupo como
(?<-word>...)
, a última captura é retirada da pilhaword
se a subexpressão...
corresponder. Então, se mudarmos nossa expressão anterior paraEntão o segundo grupo irá estourar a captura do primeiro grupo, e nós receberemos um vazio
CaptureCollection
no final. Claro, este exemplo é bastante inútil.Mas há mais um detalhe na sintaxe de menos: se a pilha já estiver vazia, o grupo falha (independentemente de seu subpadrão). Podemos alavancar esse comportamento para contar os níveis de aninhamento - e é daí que vem o nome do grupo de balanceamento (e de onde fica interessante). Digamos que queremos combinar strings que estão corretamente entre parênteses. Empurramos cada parêntese de abertura na pilha e exibimos uma captura para cada parêntese de fechamento. Se encontrarmos muitos parênteses de fechamento, ele tentará estourar uma pilha vazia e fará com que o padrão falhe:
Portanto, temos três alternativas em uma repetição. A primeira alternativa consome tudo o que não seja um parêntese. A segunda alternativa corresponde a
(
s enquanto os empurra para a pilha. A terceira alternativa corresponde a)
s enquanto remove elementos da pilha (se possível!).Nota: Apenas para esclarecer, estamos apenas verificando se não há parênteses incompatíveis! Isso significa que a string não contém parênteses vai combinar, porque eles ainda são sintaticamente válido (em alguns sintaxe, onde você precisa de seus parênteses à partida). Se você quiser garantir pelo menos um conjunto de parênteses, basta adicionar um lookahead
(?=.*[(])
logo após o^
.No entanto, esse padrão não é perfeito (ou totalmente correto).
Final: Padrões Condicionais
Há mais um problema: isso não garante que a pilha esteja vazia no final da string (portanto,
(foo(bar)
seria válida). .NET (e muitos outros sabores) tem mais uma construção que nos ajuda aqui: padrões condicionais. A sintaxe geral éonde o
falsePattern
é opcional - se for omitido, o caso falso sempre corresponderá. A condição pode ser um padrão ou o nome de um grupo de captura. Vou me concentrar no último caso aqui. Se for o nome de um grupo de captura,truePattern
será usado se e somente se a pilha de captura para aquele grupo específico não estiver vazia. Ou seja, um padrão condicional como(?(name)yes|no)
lê "sename
combinou e capturou algo (que ainda está na pilha), use o padrão,yes
caso contrário, use o padrãono
".Portanto, no final do nosso padrão acima, poderíamos adicionar algo como
(?(Open)failPattern)
que faz com que todo o padrão falhe, se aOpen
pilha não estiver vazia. A coisa mais simples para fazer o padrão falhar incondicionalmente é(?!)
(um lookahead negativo vazio). Portanto, temos nosso padrão final:Observe que essa sintaxe condicional não tem nada a ver com grupos de balanceamento, mas é necessário aproveitar todo o seu poder.
A partir daqui, o céu é o limite. Muitos usos muito sofisticados são possíveis e há alguns truques quando usados em combinação com outros recursos .NET-Regex como lookbehinds de comprimento variável ( que eu tive que aprender da maneira mais difícil ). A principal questão, entretanto, é sempre: seu código ainda pode ser mantido ao usar esses recursos? Você precisa documentar muito bem e certificar-se de que todos que trabalham nele também estão cientes desses recursos. Caso contrário, você pode se sair melhor, apenas percorrendo a string manualmente, caractere por caractere, e contando os níveis de aninhamento em um inteiro.
Adendo: O que há com o
(?<A-B>...)
sintaxe?Os créditos por esta parte vão para Kobi (veja sua resposta abaixo para mais detalhes).
Agora, com todos os itens acima, podemos validar se uma string está corretamente entre parênteses. Mas seria muito mais útil se pudéssemos realmente obter capturas (aninhadas) para o conteúdo de todos os parênteses. Claro, poderíamos nos lembrar de abrir e fechar parênteses em uma pilha de captura separada que não é esvaziada e, em seguida, fazer alguma extração de substring com base em suas posições em uma etapa separada.
Mas o .NET fornece mais um recurso de conveniência aqui: se usarmos
(?<A-B>subPattern)
, não apenas uma captura é removida da pilhaB
, mas também tudo entre essa captura suspensaB
e este grupo atual é colocado na pilhaA
. Portanto, se usarmos um grupo como este para fechar os parênteses, ao retirar os níveis de aninhamento de nossa pilha, também podemos enviar o conteúdo do par para outra pilha:Kobi forneceu esta demonstração ao vivo em sua resposta
Então, juntando todas essas coisas, podemos:
Tudo em uma única expressão regular. Se isso não for emocionante ...;)
Alguns recursos que achei úteis quando os conheci:
fonte
Apenas um pequeno acréscimo à excelente resposta de M. Buettner:
Qual é o problema com a
(?<A-B>)
sintaxe?(?<A-B>x)
é sutilmente diferente de(?<-A>(?<B>x))
. Eles resultam no mesmo fluxo de controle * , mas capturam de forma diferente.Por exemplo, vamos examinar um padrão para chaves balanceadas:
No final da partida, temos uma string balanceada, mas é tudo o que temos - não sabemos onde estão as chaves porque a
B
pilha está vazia. O trabalho árduo que o motor fez por nós acabou.( exemplo em Regex Storm )
(?<A-B>x)
é a solução para esse problema. Quão? Ele não capturarx
em$A
: ele captura o conteúdo entre a captura anteriorB
e a posição atual.Vamos usá-lo em nosso padrão:
Isso seria capturado nas
$Content
cordas entre as chaves (e suas posições), para cada par ao longo do caminho.Para a cadeia
{1 2 {3} {4 5 {6}} 7}
haveria quatro capturas:3
,6
,4 5 {6}
, e1 2 {3} {4 5 {6}} 7
- muito melhor do que nada ou}
}
}
}
.( exemplo - clique na
table
guia e veja as${Content}
capturas )Na verdade, ele pode ser usado sem qualquer equilíbrio:
(?<A>).(.(?<Content-A>).)
captura os dois primeiros personagens, mesmo que eles estejam separados por grupos.(um lookahead é mais comumente usado aqui, mas nem sempre é dimensionado: pode duplicar sua lógica.)
(?<A-B>)
é um recurso forte - dá a você controle exato sobre suas capturas. Lembre-se disso quando estiver tentando obter mais do seu padrão.fonte
|'[^']*'
no lugar certo: exemplo . Se você também precisar de caracteres de escape, há um exemplo aqui: (Regex para correspondência de literais de string C #) [ stackoverflow.com/a/4953878/7586] .