Dicas para Regex Golf

43

Semelhante aos nossos tópicos para dicas de golfe específicas ao idioma: quais são os truques gerais para encurtar expressões regulares?

Eu posso ver três usos do regex quando se trata de golfe: clássico regex golf ("aqui está uma lista que deve corresponder e aqui está uma lista que deve falhar"), usando o regex para resolver problemas computacionais e expressões regulares usadas como partes de código de golfe maior. Sinta-se à vontade para postar dicas sobre qualquer uma ou todas elas. Se sua dica estiver limitada a um ou mais sabores, indique-os na parte superior.

Como sempre, siga uma dica (ou família de dicas muito relacionadas) por resposta, para que as dicas mais úteis possam subir ao topo por meio de votação.

Martin Ender
fonte
Autopromoção flagrante: em que categoria de uso de expressões regulares isso se enquadra? codegolf.stackexchange.com/a/37685/8048
Kyle Strand
@KyleStrand "expressões regulares usadas como partes de um código de golfe maior".
Martin Ender

Respostas:

24

Quando não escapar

Essas regras se aplicam à maioria dos sabores, se não a todos:

  • ] não precisa escapar quando é incomparável.

  • {e }não precisa escapar quando não faz parte de uma repetição, por exemplo, {a}combina {a}literalmente. Mesmo se você quiser combinar algo como {2}, você só precisa escapar de um deles, por exemplo {2\}.

Nas classes de personagens:

  • ]não precisa escapar quando é o primeiro caractere de um conjunto de caracteres, por exemplo, []abc]corresponde a um de ]abc, ou quando é o segundo caractere após a ^, por exemplo, [^]]corresponde a qualquer coisa, exceto ]. (Exceção notável: sabor ECMAScript!)

  • [não precisa escapar. Juntamente com a dica acima, isso significa que você pode combinar os dois colchetes com a classe de personagem horrivelmente contra-intuitiva [][].

  • ^não precisa escapar quando é não o primeiro caractere em um conjunto de caracteres, por exemplo [ab^c].

  • -não precisa escapar quando é o primeiro (segundo após a ^) ou o último caractere em um conjunto de caracteres, por exemplo [-abc], [^-abc]ou [abc-].

  • Nenhum outro personagem precisa escapar dentro de uma classe de caracteres, mesmo que sejam meta caracteres fora das classes de caracteres (exceto a \própria barra invertida ).

Além disso, em alguns sabores ^e $são correspondidos literalmente quando não estão no início ou no final da regex, respectivamente.

(Obrigado a @ MartinBüttner por preencher alguns detalhes)

Sp3000
fonte
Alguns preferem escapar do ponto real ao incluí-lo em uma classe de caracteres em que ele não precisa ser escapado (por exemplo, [.]). Escapando que normalmente iria salvar 1 byte neste caso\.
CSᵠ
Observe que [deve ser escapado em Java. Porém, não tenho certeza sobre o ICU (usado no Android e iOS) ou .NET.
N
18

Uma expressão regular simples para corresponder a todos os caracteres imprimíveis na tabela ASCII .

[ -~]
hwnd
fonte
1
pura grandiosidade, todos os caracteres de um teclado americano padrão! Nota: a tabela ASCII padrão (não incluindo a faixa estendida 127-255
CSᵠ 7/15/15
Uso-o com frequência, mas está faltando um caractere "comum" comum: TAB. E assume que você está usando LC_ALL = "C" (ou similar), pois alguns outros códigos de idioma falharão.
Olivier Dulac
O hífen pode ser usado assim para especificar qualquer intervalo de caracteres na tabela ASCII? Isso funciona para todos os tipos de expressões regulares?
Josh Withee
14

Conheça seus sabores regex

Há uma quantidade surpreendente de pessoas que pensam que expressões regulares são essencialmente independentes da linguagem. No entanto, existem diferenças bastante substanciais entre os sabores e, especialmente para o código de golfe, é bom conhecer alguns deles e seus recursos interessantes, para que você possa escolher o melhor para cada tarefa. Aqui está uma visão geral de vários sabores importantes e o que os diferencia dos outros. (Esta lista não pode realmente estar completa, mas deixe-me saber se eu perdi algo realmente flagrante.)

Perl e PCRE

Estou jogando isso em um único pote, pois não estou muito familiarizado com o sabor Perl e eles são basicamente equivalentes (PCRE é para expressões regulares compatíveis com Perl, afinal). A principal vantagem do sabor Perl é que você pode realmente chamar o código Perl de dentro do regex e da substituição.

  • Recursão / sub-rotinas . Provavelmente a característica mais importante para o golfe (que só existe em alguns sabores).
  • Padrões condicionais (?(group)yes|no).
  • Suportes mudar de caso no texto de substituição com \l, \u, \Le \U.
  • O PCRE permite alternância em lookbehinds, onde cada alternativa pode ter um comprimento diferente (mas fixo). (A maioria dos sabores, incluindo Perl, exige que os lookbehinds tenham um comprimento fixo geral.)
  • \G para ancorar uma partida no final da partida anterior.
  • \K para redefinir o início da partida
  • O PCRE suporta propriedades de caracteres Unicode e scripts .
  • \Q...\Epara escapar de séries mais longas de caracteres. Útil quando você está tentando combinar uma string que contém muitos meta-caracteres.

.LÍQUIDO

Este é provavelmente o sabor mais poderoso, com muito poucas deficiências.

Uma falha importante em termos de golfe é que ele não suporta quantificadores possessivos como alguns outros sabores. Em vez de .?+ter que escrever (?>.?).

Java

  • Devido a um erro (consulte o Apêndice), Java suporta um tipo limitado de lookbehind de comprimento variável: você pode olhar por trás até o início da string, .*de onde você pode iniciar uma lookahead, como (?<=(?=lookahead).*).
  • Suporta união e interseção de classes de caracteres.
  • Possui o suporte mais amplo para Unicode, com classes de caracteres para "scripts Unicode, blocos, categorias e propriedades binárias" .
  • \Q...\E como no Perl / PCRE.

Rubi

Nas versões recentes, esse sabor é igualmente poderoso como o PCRE, incluindo o suporte para chamadas de sub-rotina. Como Java, também suporta união e interseção de classes de caracteres. Um recurso especial é a classe de caracteres interna para dígitos hexadecimais: \h(e negada \H).

O recurso mais útil para jogar golfe é como Ruby lida com quantificadores. Mais notavelmente, é possível aninhar quantificadores sem parênteses. .{5,7}+funciona e o mesmo acontece .{3}?. Além disso, ao contrário da maioria dos outros sabores, se o limite inferior de um quantificador for, 0ele pode ser omitido, por exemplo, .{,5}é equivalente a .{0,5}.

Quanto à sub-rotinas, a principal diferença entre sub-rotinas de PCRE e sub-rotinas de Ruby, é que a sintaxe de Ruby é um byte mais (?n)vs \g<n>, mas sub-rotinas de Ruby pode ser usado para capturar, enquanto PCRE redefine capturas após uma sub-rotina acabamentos.

Finalmente, Ruby possui semânticas diferentes para modificadores relacionados à linha do que a maioria dos outros tipos. O modificador que geralmente é chamado mem outros sabores está sempre ativado no Ruby. Assim, ^e $sempre coincidir com o início eo fim de uma linha e não apenas o início e fim da cadeia. Isso pode economizar um byte se você precisar desse comportamento, mas custará bytes extras se não precisar, porque você precisará substituir ^e $por \Ae \z, respectivamente. Além disso, o modificador que normalmente é chamado s(que faz .corresponder os feeds de linha) é chamado mno Ruby. Isso não afeta a contagem de bytes, mas deve-se ter em mente para evitar confusão.

Python

O Python tem um sabor sólido, mas não conheço nenhum recurso particularmente útil que você não encontraria em nenhum outro lugar.

No entanto , há um sabor alternativo que se destina a substituir o remódulo em algum momento e que contém muitos recursos interessantes. Além de adicionar suporte para recursão, lookbehinds de comprimento variável e operadores de combinação de classes de caracteres, ele também possui o recurso exclusivo de correspondência difusa . Em essência, você pode especificar vários erros (inserções, exclusões, substituições) que são permitidos, e o mecanismo também fornecerá correspondências aproximadas.

ECMAScript

O sabor do ECMAScript é muito limitado e, portanto, raramente é muito útil para jogar golfe. A única coisa que ele tem a oferecer é a classe de caracteres vazios negada[^] para corresponder a qualquer caractere, bem como a classe de caracteres vazios com falha incondicional [](ao contrário do habitual (?!)). Infelizmente, o sabor não possui nenhum recurso que o torne útil para problemas normais.

Lua

Lua tem seu próprio sabor bastante único, que é bastante limitado (por exemplo, você não pode nem quantificar grupos), mas vem com um punhado de recursos úteis e interessantes.

  • Possui um grande número de atalhos para classes de caracteres incorporadas , incluindo pontuação, caracteres maiúsculos / minúsculos e dígitos hexadecimais.
  • Com %bele, suporta uma sintaxe muito compacta para corresponder a cadeias equilibradas. Por exemplo, %b()corresponde a (e então tudo até uma correspondência )(pulando corretamente os pares correspondentes internos). (e )pode haver dois caracteres aqui.

Impulso

O sabor regex do Boost é essencialmente do Perl. No entanto, possui alguns recursos novos e agradáveis ​​para a substituição de expressões regulares, incluindo alterações de casos e condicionais . O último é exclusivo do Boost, até onde eu sei.

Martin Ender
fonte
Observe que o olhar à frente no olhar para trás ultrapassará o limite limite no olhar para trás. Testado em Java e PCRE.
N
Não é .?+equivalente a .*?
CalculatorFeline
@CalculatorFeline O primeiro é um quantificador possessivo de 0 ou 1 (em sabores que suportam quantificadores possessivos), o segundo é um quantificador de 0 ou mais.
Martin Ender
@CalculatorFeline ah eu entendo a confusão. Houve um erro de digitação.
Martin Ender
13

Conheça suas classes de personagens

A maioria dos tipos de expressões regulares tem classes de caracteres predefinidas. Por exemplo, \dcorresponde a um dígito decimal, que é três bytes menor que [0-9]. Sim, eles podem ser um pouco diferentes, pois \dtambém podem corresponder aos dígitos Unicode em alguns tipos, mas para a maioria dos desafios isso não fará diferença.

Aqui estão algumas classes de caracteres encontradas na maioria dos tipos de expressões regulares:

\d      Match a decimal digit character
\s      Match a whitespace character
\w      Match a word character (typically [a-zA-Z0-9_])

Além disso, também temos:

\D \S \W

que são versões negadas do acima.

Certifique-se de verificar o seu sabor para qualquer classe de personagem adicional que possa ter. Por exemplo, o PCRE possui \Rpara novas linhas e Lua ainda possui classes como caracteres minúsculos e maiúsculos.

(Obrigado a @HamZa e @ MartinBüttner por apontar isso)

Sp3000
fonte
3
\Rpara novas linhas no PCRE.
precisa saber é o seguinte
12

Não se preocupe com grupos que não capturam (a menos que ...)

Esta dica se aplica a (pelo menos) todos os sabores populares de inspiração Perl.

Isso pode ser óbvio, mas (quando não estiver jogando golfe) é uma boa prática usar grupos (?:...)que não capturam sempre que possível. Esses dois personagens extras ?:são um desperdício ao jogar no golfe; portanto, use grupos de captura, mesmo que você não os refaça.

Porém, há uma exceção (rara): se você voltar ao grupo de referência 10pelo menos três vezes, poderá salvar bytes transformando um grupo anterior em um grupo não capturável, de forma que todos eles \10se tornem \9s. (Truques semelhantes se aplicam, se você usar o grupo 11pelo menos 5 vezes e assim por diante.)

Martin Ender
fonte
Por que o 11 precisa de 5 vezes para valer a pena quando 10 requer 3?
22616 Nic Hartley
1
O @QPaysTaxes pode usar em $9vez de $10ou $11uma vez salva um byte. Transformar $10em $9requer um ?:, que é de dois bytes, então você precisará de três $10s para salvar alguma coisa. Transformar $11em $9requer dois ?:s, que são quatro bytes, portanto, você precisará de cinco $11s para salvar algo (ou cinco $10e $11combinados).
Martin Ender
10

Recursão para reutilização de padrões

Um punhado de sabores apóia a recursão ( que eu saiba , Perl, PCRE e Ruby). Mesmo quando você não está tentando resolver problemas recursivos, esse recurso pode economizar muitos bytes em padrões mais complicados. Não há necessidade de fazer a chamada para outro grupo (nomeado ou numerado) dentro desse próprio grupo. Se você tem um determinado padrão que aparece várias vezes em sua regex, basta agrupá-lo e consultá-lo fora desse grupo. Isso não é diferente de uma chamada de sub-rotina nas linguagens de programação normais. Então, ao invés de

...someComplexPatternHere...someComplexPatternHere...someComplexPatternHere... 

No Perl / PCRE, você poderia fazer:

...(someComplexPatternHere)...(?1)...(?1)...

ou em Ruby:

...(someComplexPatternHere)...\g<1>...\g<1>...

desde que seja o primeiro grupo (é claro, você pode usar qualquer número na chamada recursiva).

Observe que isso não é o mesmo que uma referência anterior ( \1). As referências anteriores correspondem exatamente à mesma string que o grupo encontrou na última vez. Essas chamadas de sub-rotina realmente avaliam o padrão novamente. Como um exemplo para someComplexPatternHereobter uma longa classe de caracteres:

a[0_B!$]b[0_B!$]c[0_B!$]d

Isso corresponderia a algo como

aBb0c!d

Observe que você não pode usar referências anteriores aqui enquanto preserva o comportamento. Uma referência anterior falharia na sequência acima, porque Be 0e !não são os mesmos. No entanto, com chamadas de sub-rotina, o padrão é realmente reavaliado. O padrão acima é completamente equivalente a

a([0_B!$])b(?1)c(?1)d

Captura em chamadas de sub-rotina

Uma nota de cautela para Perl e PCRE: se o grupo 1nos exemplos acima contiver mais grupos, as chamadas de sub-rotina não lembrarão de suas capturas. Considere este exemplo:

(\w(\d):)\2 (?1)\2 (?1)\2

Isso não corresponde

x1:1 y2:2 z3:3

porque depois que as chamadas de sub-rotina retornam, a nova captura de grupo 2é descartada. Em vez disso, esse padrão corresponderia a esta sequência:

x1:1 y2:1 z3:1

Isto é diferente de Ruby, onde as chamadas de sub-rotinas fazer reter as suas capturas, de modo que o regex Rubi equivalente (\w(\d):)\2 \g<1>\2 \g<1>\2iria coincidir com o primeiro dos exemplos acima.

Martin Ender
fonte
Você pode usar \1para Javascript. E PHP também (eu acho).
Ismael Miguel
5
@IsmaelMiguel Esta não é uma referência anterior. Isso realmente avalia o padrão novamente. Por exemplo (..)\1, corresponderá, ababmas falhará, abbaenquanto (..)(?1)corresponderá ao último. Na verdade, é uma chamada de sub-rotina no sentido de que a expressão é aplicada novamente, em vez de corresponder literalmente ao que correspondeu da última vez.
Martin Ender
Uau, eu não tinha ideia! Aprender algo novo todos os dias
Ismael Miguel
No .NET (ou em outros sabores sem esse recurso):(?=a.b.c)(.[0_B!$]){3}d
jimmy23013
@ user23013 que parece muito específico para este exemplo em particular. Não sei se isso é aplicável se eu reutilizar um determinado subpadrão em várias soluções.
Martin Ender
9

Fazendo com que uma correspondência falhe

Ao usar o regex para resolver problemas computacionais ou corresponder a linguagens altamente não regulares, às vezes é necessário fazer com que uma ramificação do padrão falhe, independentemente de onde você estiver na sequência. A abordagem ingênua é usar um lookahead negativo vazio:

(?!)

O conteúdo (o padrão vazio) sempre corresponde, portanto a aparência negativa sempre falha. Mas, na maioria das vezes, existe uma opção muito mais simples: basta usar um caractere que você sabe que nunca aparecerá na entrada. Por exemplo, se você sabe que sua entrada sempre será composta apenas por dígitos, você pode simplesmente usar

!

ou qualquer outro caractere não-dígito e não meta para causar falha.

Mesmo que sua entrada possa conter substrings, existem maneiras mais curtas que (?!). Qualquer sabor que permita que as âncoras apareçam dentro de um padrão, em oposição ao final, pode usar uma das seguintes soluções de 2 caracteres:

a^
$a

Observe, no entanto, que alguns sabores tratarão ^e $como caracteres literais nessas posições, porque obviamente não fazem sentido como âncoras.

No sabor ECMAScript, há também a solução de 2 caracteres bastante elegante

[]

Essa é uma classe de caracteres vazia, que tenta garantir que os próximos caracteres sejam um dos da classe - mas não há caracteres na classe, portanto sempre falha. Observe que isso não funcionará em nenhum outro sabor, porque as classes de caracteres geralmente não podem estar vazias.

Martin Ender
fonte
8

Otimize seus ORs

Sempre que você tiver 3 ou mais alternativas em seu RegEx:

/aliceblue|antiquewhite|aquamarine|azure/

Verifique se há um começo comum:

/a(liceblue|ntiquewhite|quamarine|zure)/

E talvez até um final comum?

/a(liceblu|ntiquewhit|quamarin|zur)e/

Nota: 3 é apenas o começo e representaria a mesma duração, 4 ou mais faria a diferença


Mas e se nem todos eles tiverem um prefixo comum? (espaço em branco adicionado apenas para maior clareza)

/aliceblue|antiquewhite|aqua|aquamarine|azure
|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood
|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan/

Agrupe-os, desde que a regra 3+ faça sentido:

/a(liceblue|ntiquewhite|qua|quamarine|zure)
|b(eige|isque|lack|lanchedalmond|lue|lueviolet|rown|urlywood)
|c(adetblue|hartreuse|hocolate|oral|ornflowerblue|ornsilk|rimson|yan)/

Ou até generalize se a entropia satisfizer o seu caso:

/\w(liceblue|ntiquewhite|qua|quamarine|zure
|eige|isque|lack|lanchedalmond|lue|lueviolet|rown|urlywood
|adetblue|hartreuse|hocolate|oral|ornflowerblue|ornsilk|rimson|yan)/

^ neste caso, temos certeza de que não obtemos nenhum clueoucrown slack Ryan

Isso "de acordo com alguns testes" também melhora o desempenho, pois fornece uma âncora para começar.

CSᵠ
fonte
1
Se o início ou o fim comum tiver mais de um caractere, até o agrupamento de dois poderá fazer a diferença. Como aqua|aquamarineaqua(|marine)ou aqua(marine)?.
Paŭlo Ebermann
6

Este é bastante simples, mas vale a pena afirmar:

Se você repetir a classe de caracteres, [a-zA-Z]provavelmente poderá usar [a-z]e acrescentar o i( modificador sensível a maiúsculas e minúsculas) ao seu regex.

Por exemplo, no Ruby, as duas regexes a seguir são equivalentes:

/[a-zA-Z]+\d{3}[a-zA-Z]+/
/[a-z]+\d{3}[a-z]/i - 7 bytes mais curtos

Nesse caso, os outros modificadores também podem diminuir o seu comprimento total. Em vez de fazer isso:

/(.|\n)/

que corresponde a qualquer caractere (porque ponto não coincide com nova linha), use as s ingle-line modificador s, que faz novas linhas jogo de pontos.

/./s - 3 bytes mais curto


Em Ruby, há uma tonelada de classes de caracteres internas para regex. Veja esta página e procure por "Propriedades dos caracteres".
Um ótimo exemplo é o "Símbolo da moeda". De acordo com a Wikipedia, existem muitos símbolos de moeda possíveis e colocá-los em uma classe de caracteres seria muito caro ( [$฿¢₡Ð₫€.....]), enquanto você pode corresponder a qualquer um deles em 6 bytes:\p{Sc}

Devon Parsons
fonte
1
Exceto JavaScript, onde o smodificador não é suportado. :( Mas lá você pode usar de propriedade da JavaScript /[^]/truque.
manatwork
Observe que (.|\n)nem funciona em alguns tipos, porque .geralmente também não corresponde a outros tipos de separadores de linha. No entanto, a maneira usual de fazer isso (sem s) é [\s\S]com os mesmos bytes que (.|\n).
Martin Ender
@ MartinBüttner, minha idéia era mantê-lo junto com a outra linha que termina dicas relacionadas. Mas se você acha que essa resposta é mais sobre modificadores, não tenho objeções se você a repassar.
Manatwork
@manatwork feito (e adicionado um truque específica correspondente não-ES bem)
Martin Enders
6

Um analisador de idioma simples

Você pode criar um analisador muito simples com um RE \d+|\w+|".*?"|\n|\S. Os tokens que você precisa corresponder são separados pelo caractere RE 'ou'.

Cada vez que o mecanismo de RE tenta corresponder à posição atual no texto, ele tenta o primeiro padrão, depois o segundo, etc. Se falhar (em um caractere de espaço aqui, por exemplo), ele segue em frente e tenta as correspondências novamente . A ordem é importante. Se colocarmos o \Stermo antes do \d+termo, \Sele corresponderá primeiro a qualquer caractere não espacial que interrompa nosso analisador.

O ".*?"correspondente de seqüência de caracteres usa um modificador não guloso, portanto, correspondemos apenas uma sequência de cada vez. Se o seu ER não possui funções não gananciosas, você pode usar o "[^"]*"que é equivalente.

Exemplo de Python:

text = 'd="dogfinder"\nx=sum(ord(c)*872 for c in "fish"+d[3:])'
pat = r'\d+|\w+|".*?"|\n|\S'
print re.findall(pat, text)

['d', '=', '"dogfinder"', '\n', 'x', '=', 'sum', '(', 'ord', '(', 'c', ')',
    '*', '872', 'for', 'c', 'in', '"fish"', '+', 'd', '[', '3', ':', ']', ')']

Exemplo de Python para golfe:

# assume we have language text in A, and a token processing function P
map(P,findall(r'\d+|\w+|".*?"|\n|\S',A))

Você pode ajustar os padrões e a ordem deles para o idioma que você precisa corresponder. Essa técnica funciona bem para JSON, HTML básico e expressões numéricas. Ele foi usado com sucesso várias vezes com o Python 2, mas deve ser geral o suficiente para funcionar em outros ambientes.

Cavaleiro Lógico
fonte
6

\K em vez de olhar positivo

PCRE e Perl suportam a sequência de escape \K, que redefine o início da partida. Ou seja ab\Kcd, exigirá que sua string de entrada contenha, abcdmas a correspondência relatada será apenas cd.

Se você estiver usando uma observação positiva no início de seu padrão (que provavelmente é o local mais provável), na maioria dos casos, poderá usar \Ke salvar 3 bytes:

(?<=abc)def
abc\Kdef

Isso é equivalente para a maioria dos propósitos, mas não totalmente. As diferenças trazem vantagens e desvantagens com eles:

  • De cabeça para baixo: PCRE e Perl não suportam lookbehinds de comprimento arbitrário (somente o .NET suporta). Ou seja, você não pode fazer algo assim (?<=ab*). Mas com \Kvocê pode colocar qualquer tipo de padrão na frente dele! Então ab*\Kfunciona. Isso realmente torna essa técnica muito mais poderosa nos casos em que é aplicável.
  • De cabeça para baixo: as pesquisas de retorno não voltam atrás. Isso é relevante se você deseja capturar algo na parte posterior para voltar atrás, mas existem várias capturas possíveis que levam a correspondências válidas. Nesse caso, o mecanismo regex tentaria apenas uma dessas possibilidades. Ao usar \Kessa parte do regex, está sendo retornado como todo o resto.
  • Desvantagem: como você provavelmente sabe, várias correspondências de uma regex não podem se sobrepor. Freqüentemente, as pesquisas são usadas para contornar parcialmente essa limitação, pois o pesquisador pode validar uma parte da sequência que já foi consumida por uma correspondência anterior. Portanto, se você quiser combinar todos os caracteres que se seguiram, ab poderá usar (?<=ab).. Dada a entrada

    ababc
    

    isso corresponderia ao segundo ae ao c. Isso não pode ser reproduzido com \K. Se você usasse ab\K., obteria apenas a primeira correspondência, porque agora ela abnão está em uma visão geral.

Martin Ender
fonte
Se um padrão usa a \Ksequência de escape dentro de uma afirmação positiva, o início relatado de uma correspondência bem-sucedida pode ser maior que o final da correspondência.
hwnd
@hwnd Meu argumento é que ababc, dado , não há como combinar o segundo ae o ccom \K. Você receberá apenas uma partida.
Martin Ender
Você está correto, não com o recurso em si. Você teria que ancorar com\G
hwnd
@hwnd Ah, eu vejo o seu ponto agora. Mas acho que, nesse ponto (do ponto de vista do golfe), você está melhor com um olhar negativo por trás, porque você pode até precisar mesmo assim, pois não pode ter certeza de que .a última partida foi realmente um a.
Martin Ender
1
Uso interessante de \ K =)
hwnd
5

Correspondendo a qualquer caractere

O sabor do ECMAScript não possui os smodificadores que fazem a .correspondência com qualquer caractere (incluindo novas linhas). Isso significa que não há solução de caractere único para corresponder caracteres completamente arbitrários. A solução padrão em outros sabores (quando não se deseja usar spor algum motivo) é [\s\S]. No entanto, ECMAScript é o único sabor (que eu saiba) que suporta classes de personagens vazias, e, portanto, tem uma alternativa muito mais curto: [^]. Esta é uma classe de caracteres vazia negada - ou seja, corresponde a qualquer caractere.

Mesmo para outros sabores, podemos aprender com esta técnica: se não quisermos usá-lo s(por exemplo, porque ainda precisamos do significado usual .em outros lugares), ainda haverá uma maneira mais curta de corresponder os caracteres de nova linha e de impressão, desde que exista algum caractere que sabemos que não aparece na entrada. Digamos, estamos processando números delimitados por novas linhas. Então podemos combinar qualquer caractere [^!], pois sabemos que !isso nunca fará parte da string. Isso economiza dois bytes sobre o ingênuo [\s\S]ou [\d\n].

Martin Ender
fonte
4
No Perl, \Nsignifica exatamente o que .significa fora do /smodo, exceto que não é afetado por um modo.
21978 Konrad Borowski
4

Use grupos atômicos e quantificadores possessivos

Eu encontrei grupos atômicos ( (?>...)) e quantificadores possessivo ( ?+, *+, ++, {m,n}+), por vezes muito úteis para o golfe. Ele corresponde a uma sequência e não permite o retorno posterior. Portanto, ele corresponderá apenas à primeira sequência correspondível encontrada pelo mecanismo de expressão regular.

Por exemplo: Para combinar uma sequência com um número ímpar de a's no início, que não é seguido por mais a' s, você pode usar:

^(aa)*+a
^(?>(aa)*)a

Isso permite que você use coisas como .*livremente e, se houver uma correspondência óbvia, não haverá outra possibilidade de corresponder a muitos ou poucos caracteres, o que pode quebrar seu padrão.

No regex do .NET (que não possui quantificadores possessivos), você pode usá-lo para exibir o grupo 1 o maior múltiplo de três (com no máximo 30) vezes (sem jogar muito bem):

(?>((?<-1>){3}|){10})
jimmy23013
fonte
1
O ECMAscript também está faltando quantificadores possessivos ou grupos atômicos :(
CSᵠ
4

Esqueça um grupo capturado após uma subexpressão (PCRE)

Para esta regex:

^((a)(?=\2))(?!\2)

Se você deseja limpar o \ 2 após o grupo 1, poderá usar a recursão:

^((a)(?=\2)){0}(?1)(?!\2)

Ele corresponderá aaenquanto o anterior não. Às vezes você também pode usar ??ou mesmo ?no lugar de {0}.

Isso pode ser útil se você tiver usado muitas recursões e algumas das referências anteriores ou grupos condicionais aparecerem em locais diferentes no seu regex.

Observe também que grupos atômicos são assumidos para recursões no PCRE. Portanto, isso não corresponde a uma única letra a:

^(a?){0}(?1)a

Ainda não experimentei em outros sabores.

Para lookaheads, você também pode usar negativos duplos para esta finalidade:

^(?!(?!(a)(?=\1))).(?!\1)
jimmy23013
fonte
4

Expressões opcionais

Às vezes é útil lembrar que

(abc)?

é basicamente o mesmo que

(abc|)

Porém, há uma pequena diferença: no primeiro caso, o grupo captura abcou não captura. O último caso faria com que uma referência anterior falhasse incondicionalmente. Na segunda expressão, o grupo capturará abcou uma sequência vazia, onde o último caso faria uma correspondência de referência anterior incondicionalmente. Para emular o último comportamento, ?você precisará cercar tudo em outro grupo que custaria dois bytes:

((abc)?)

A versão usando |também é útil quando você deseja agrupar a expressão em alguma outra forma de grupo e não se importa com a captura:

(?=(abc)?)
(?=abc|)

(?>(abc)?)
(?>abc|)

Por fim, esse truque também pode ser aplicado a ungreedy, ?onde ele salva um byte mesmo em sua forma bruta (e consequentemente 3 bytes quando combinado com outras formas de grupos):

(abc)??
(|abc)
Martin Ender
fonte
1

Vários lookaheads que sempre correspondem (.NET)

Se você tiver três ou mais construções de lookahead que sempre correspondem (para capturar subexpressões) ou se houver um quantificador em um lookahead seguido por outra coisa, eles devem estar em um grupo não necessariamente capturado:

(?=a)(?=b)(?=c)
((?=a)b){...}

Estes são mais curtos:

(?(?(?(a)b)c))
(?(a)b){...}

onde anão deve ser o nome de um grupo capturado. Você não pode usar o |significado usual be csem adicionar outro par parênteses.

Infelizmente, os grupos de equilíbrio nas condicionais pareciam bugs, tornando-os inúteis em muitos casos.

jimmy23013
fonte