Conseils pour Regex Golf

43

Semblables à nos discussions pour des astuces de golf spécifiques à une langue: quels sont les astuces générales pour raccourcir les expressions régulières?

Je peux voir trois utilisations de regex quand il s’agit de jouer au golf: le regex golf classique ("voici une liste qui devrait correspondre, et une liste qui devrait échouer"), en utilisant regex pour résoudre des problèmes de calcul et des expressions régulières utilisées plus gros code de golf. Ne hésitez pas à poster des conseils pour tout ou partie de ceux-ci. Si votre pourboire se limite à une ou plusieurs saveurs, veuillez indiquer ces saveurs en haut.

Comme d'habitude, veuillez vous en tenir à un conseil (ou à une famille de conseils très liés) par réponse, afin que les conseils les plus utiles puissent atteindre le sommet via le vote.

Martin Ender
la source
Auto-promotion flagrante: à quelle catégorie d'utilisation des regex appartient-elle? codegolf.stackexchange.com/a/37685/8048
Kyle Strand
@KyleStrand "expressions régulières utilisées en tant que parties d'un code de golf plus important."
Martin Ender

Réponses:

24

Quand ne pas s'échapper

Ces règles s'appliquent à la plupart des saveurs, sinon à toutes:

  • ] n'a pas besoin de s'échapper lorsqu'il est sans pareil.

  • {et }n'ont pas besoin de s'échapper quand ils ne font pas partie d'une répétition, par exemple des {a}allumettes au {a}sens littéral. Même si vous voulez associer quelque chose du genre {2}, il vous suffit d'échapper à l'un d'eux, par exemple {2\}.

En cours de personnage:

  • ]n'a pas besoin de s'échapper lorsqu'il s'agit du premier caractère d'un jeu de caractères, par exemple, []abc]correspond à l'un des ]abc, ou lorsqu'il s'agit du deuxième caractère après un ^, par exemple [^]]correspond à tout sauf ]. (Exception notable: saveur ECMAScript!)

  • [n'a pas besoin de s'échapper du tout. Avec le conseil ci-dessus, cela signifie que vous pouvez associer les deux supports à la classe de caractères horriblement contre-intuitive [][].

  • ^n'a pas besoin de s'échapper lorsqu'il ne s'agit pas du premier caractère d'un jeu de caractères, par exemple [ab^c].

  • -n'a pas besoin d'échapper s'il s'agit du premier (deuxième après un ^) ou du dernier caractère d'un jeu de caractères, par exemple [-abc], [^-abc]ou [abc-].

  • Aucun autre caractère n'a besoin de s'échapper à l'intérieur d'une classe de caractère, même s'il s'agit de méta-caractères en dehors des classes de caractères (à l'exception de la barre oblique inverse \elle-même).

En outre, dans certains goûts ^et $sont appariés littéralement quand ils ne sont pas respectivement au début ou à la fin de la regex.

(Merci à @ MartinBüttner d'avoir fourni quelques détails)

Sp3000
la source
Certains préfèrent échapper au point réel en l'enfermant dans une classe de caractères où il n'est pas nécessaire d'échapper (par exemple [.]). Echapper normalement sauverait 1 octet dans ce cas\.
CSᵠ
Notez que cela [doit être échappé en Java. Pas sûr de l'ICU (utilisé dans Android et iOS) ou .NET, cependant.
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳
18

Une expression régulière simple pour faire correspondre tous les caractères imprimables de la table ASCII .

[ -~]
hwnd
la source
1
pure génialité, tous les caractères d'un clavier américain standard! remarque: la table ascii standard (non compris la plage étendue 127-255
CS du
Je l'utilise souvent, mais il manque un caractère "ordinaire" commun: TAB. Et cela suppose que vous utilisez LC_ALL = "C" (ou similaire), car d'autres paramètres régionaux échoueront.
Olivier Dulac
Le trait d'union peut-il être utilisé comme cela pour spécifier une plage de caractères dans la table ASCII? Est-ce que cela fonctionne pour tous les goûts de regex?
Josh Withee
14

Connaissez vos goûts regex

Un nombre surprenant de personnes pensent que les expressions régulières sont essentiellement agnostiques. Cependant, il existe des différences assez importantes entre les saveurs, et en particulier pour le golf de code, il est bon de connaître quelques-unes d'entre elles, ainsi que leurs caractéristiques intéressantes, afin que vous puissiez choisir le meilleur pour chaque tâche. Voici un aperçu de plusieurs saveurs importantes et de ce qui les distingue des autres. (Cette liste ne peut pas être vraiment complète, mais laissez-moi savoir si j'ai raté quelque chose de vraiment flagrant.)

Perl et PCRE

Je les jette dans un seul récipient, car je ne connais pas très bien la saveur Perl, qui est la plupart du temps équivalente (PCRE est après tout des expressions régulières compatibles Perl). L'avantage principal de la version Perl est que vous pouvez appeler du code Perl depuis l'intérieur de la regex et de la substitution.

  • Récursion / sous-routines . Probablement la caractéristique la plus importante pour le golf (qui n'existe que dans quelques saveurs).
  • Modèles conditionnels (?(group)yes|no).
  • Soutient le changement de cas dans la chaîne de remplacement avec \l, \u, \Let \U.
  • PCRE permet l’alternance en regard, chaque alternative pouvant avoir une longueur différente (mais fixe). (La plupart des saveurs, y compris Perl, exigent que les contours aient une longueur totale).
  • \G d'ancrer un match à la fin du match précédent.
  • \K réinitialiser le début du match
  • PCRE prend en charge les propriétés de caractères Unicode et les scripts .
  • \Q...\Epour échapper à de plus longues séries de caractères. Utile lorsque vous essayez de faire correspondre une chaîne contenant de nombreux méta-caractères.

.NET

C'est probablement la saveur la plus puissante, avec seulement très peu d'inconvénients.

Une lacune importante en termes de golf est qu’elle ne prend pas en charge les quantificateurs possessifs comme certains autres parfums. Au lieu de cela, .?+vous devrez écrire (?>.?).

Java

  • Suite à un bogue (voir Annexe), Java prend en charge un type limité de recherche de longueur variable: vous pouvez regarder au- .*delà du début de la chaîne, d'où vous pouvez maintenant commencer une recherche, comme (?<=(?=lookahead).*).
  • Prend en charge l'union et l'intersection des classes de caractères.
  • Possède le support le plus complet pour Unicode, avec des classes de caractères pour les "scripts Unicode, blocs, catégories et propriétés binaires" .
  • \Q...\E comme dans Perl / PCRE.

Rubis

Dans les versions récentes, cette version est aussi puissante que PCRE, y compris la prise en charge des appels de sous-routines. Comme Java, il prend également en charge l'union et l'intersection de classes de caractères. Une caractéristique spéciale est la classe de caractères intégrée pour les chiffres hexadécimaux: \h(et le négatif \H).

La fonctionnalité la plus utile pour jouer au golf est cependant la façon dont Ruby gère les quantificateurs. Plus particulièrement, il est possible d'imbriquer des quantificateurs sans parenthèses. .{5,7}+fonctionne et fait ainsi .{3}?. En outre, contrairement à la plupart des autres versions, si la limite inférieure d’un quantificateur est définie, 0elle peut être omise, par exemple .{,5}équivalente à .{0,5}.

En ce qui concerne les sous - routines, la différence majeure entre les sous - routines de PCRE et les sous - routines de Ruby, est que la syntaxe de Ruby est un octet plus (?n)vs \g<n>, mais les sous - routines de Ruby peut être utilisé pour la capture, alors que PCRE remet à zéro la capture après une finition de sous - programme.

Enfin, Ruby a une sémantique différente pour les modificateurs liés aux lignes de la plupart des autres versions. Le modificateur généralement appelé mdans d'autres versions est toujours activé dans Ruby. Donc ^, faites $toujours correspondre le début et la fin d'une ligne, pas seulement le début et la fin de la chaîne. Cela peut vous faire économiser un octet si vous avez besoin de ce comportement, mais cela vous coûtera des octets supplémentaires si vous ne le faites pas, car vous devrez remplacer ^et $par \Aet \zrespectivement. En plus de cela, le modificateur généralement appelé s(qui fait .correspondre les sauts de ligne) est appelé men Ruby. Cela n'affecte pas le nombre d'octets, mais il faut le garder à l'esprit pour éviter toute confusion.

Python

Python a une saveur solide, mais je ne connais pas de fonctionnalités particulièrement utiles que vous ne trouverez nulle part ailleurs.

Cependant , il existe une variante qui vise à remplacer le remodule à un moment donné et qui contient de nombreuses fonctionnalités intéressantes. Outre la prise en charge de la récursivité, des recherches de longueur variable et des opérateurs de combinaison de classes de caractères, il offre également la caractéristique unique de la correspondance floue . En substance, vous pouvez spécifier un certain nombre d'erreurs (insertions, suppressions, substitutions) autorisées. Le moteur vous donnera également des correspondances approximatives.

ECMAScript

La saveur ECMAScript est très limitée, et donc rarement très utile pour le golf. La seule chose qui lui va, c'est la classe de caractères vides annulée [^] pour correspondre à n'importe quel caractère, ainsi que la classe de caractères vides qui échoue inconditionnellement [](par opposition à l'habituel (?!)). Malheureusement, la saveur n'a aucune caractéristique qui rend ce dernier utile pour les problèmes normaux.

Lua

Lua a sa propre saveur assez unique, qui est assez limitée (par exemple, vous ne pouvez même pas quantifier les groupes) mais qui vient avec une poignée de fonctionnalités utiles et intéressantes.

  • Il comporte un grand nombre de raccourcis pour les classes de caractères intégrées , notamment la ponctuation, les majuscules et les minuscules et les chiffres hexadécimaux.
  • Avec %bcela prend en charge une syntaxe très compacte pour faire correspondre les chaînes équilibrées. Par exemple, %b()correspond à a (, puis à tout, jusqu'à une correspondance )(en sautant correctement les paires appariées internes). (et )peut être deux personnages ici.

Renforcer

La saveur regex de Boost est essentiellement celle de Perl. Cependant, il a quelques nouvelles fonctionnalités intéressantes pour la substitution de regex, y compris les modifications de casse et les conditions . Ce dernier est propre à Boost, à ma connaissance.

Martin Ender
la source
Notez que regarder en avant dans regarder en arrière va franchir la limite de la distance en regardant en arrière. Testé en Java et PCRE.
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳
N'est pas .?+équivalent à .*?
CalculatriceFeline
@CalculatorFeline Le premier est un quantificateur 0 ou 1 possessif (dans les arômes qui prennent en charge les quantificateurs possessifs), le dernier est un quantificateur 0 ou plus.
Martin Ender
@CalculatorFeline ah je comprends la confusion. Il y avait une faute de frappe.
Martin Ender
13

Connaissez vos classes de personnage

La plupart des saveurs de regex ont des classes de caractères prédéfinies. Par exemple, \dcorrespond à un chiffre décimal, inférieur de trois octets à [0-9]. Oui, ils peuvent être légèrement différents, car ils \dpeuvent également correspondre aux chiffres Unicode dans certaines versions, mais pour la plupart des problèmes, cela ne fera aucune différence.

Voici quelques classes de caractères trouvées dans la plupart des saveurs de regex:

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

De plus, nous avons aussi:

\D \S \W

qui sont des versions annulées de ce qui précède.

Assurez-vous de vérifier votre goût pour toutes les classes de caractères supplémentaires qu'il pourrait avoir. Par exemple, PCRE a \Rpour les nouvelles lignes et Lua a même des classes telles que les minuscules et les majuscules.

(Merci à @HamZa et @ MartinBüttner pour les signaler)

Sp3000
la source
3
\Rpour les nouvelles lignes dans PCRE.
HamZa
12

Ne vous embêtez pas avec des groupes sans capture (sauf si ...)

Cette astuce s'applique à (au moins) toutes les saveurs populaires inspirées de Perl.

Cela peut sembler évident, mais (lorsque vous ne jouez pas au golf), il est recommandé d’utiliser des groupes de non-capture (?:...)autant que possible. ?:Cependant, ces deux personnages supplémentaires sont inutiles lors de la pratique du golf. Il vous suffit donc d’utiliser des groupes de capture, même si vous ne les référez pas.

Il existe cependant une exception (rare): si vous parvenez 10à au moins trois fois à un groupe de référence arrière , vous pouvez réellement sauvegarder des octets en transformant un groupe précédent en un groupe sans capture, de sorte que tous ces \10s deviennent des \9s. (Des astuces similaires s'appliquent si vous utilisez le groupe 11au moins 5 fois, etc.).

Martin Ender
la source
Pourquoi 11 a-t-il besoin de 5 fois pour en valoir la peine alors que 10 en exigent 3?
Nic Hartley
1
@QPaysTaxes pouvant utiliser à la $9place $10ou $11une fois enregistre un octet. La conversion $10en $9nécessite un ?:, qui est de deux octets. Vous aurez donc besoin de trois $10secondes pour enregistrer quelque chose. La conversion $11en $9deux requiert ?:quatre octets, vous aurez donc besoin de cinq $11secondes pour enregistrer quelque chose (ou cinq $10et $11combinés).
Martin Ender
10

Récursion pour la réutilisation de modèles

Une poignée de saveurs supportent la récursivité ( à ma connaissance , Perl, PCRE et Ruby). Même lorsque vous n'essayez pas de résoudre des problèmes récursifs, cette fonctionnalité peut économiser beaucoup d'octets dans des modèles plus compliqués. Il n'est pas nécessaire d'appeler un autre groupe (nommé ou numéroté) à l'intérieur même de ce groupe. Si vous avez un certain motif qui apparaît plusieurs fois dans votre expression rationnelle, il vous suffit de le regrouper et de vous y référer en dehors de ce groupe. Ce n'est pas différent d'un appel de sous-routine dans les langages de programmation normaux. Donc au lieu de

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

en Perl / PCRE, vous pouvez faire:

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

ou en rubis:

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

à condition qu'il s'agisse du premier groupe (vous pouvez bien entendu utiliser n'importe quel numéro dans l'appel récursif).

Notez que ce n’est pas la même chose qu’un backreference ( \1). Les références arrière correspondent exactement à la même chaîne que la dernière fois pour le groupe. Ces appels de sous-programmes évaluent à nouveau le motif. Par exemple, someComplexPatternHereprenons une longue classe de caractères:

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

Cela correspond à quelque chose comme

aBb0c!d

Notez que vous ne pouvez pas utiliser de références arrières ici tout en préservant le comportement. Une référence arrière échouerait sur la chaîne ci-dessus, car Bet 0et !ne sont pas identiques. Cependant, avec les appels de sous-programmes, le modèle est en réalité réévalué. Le modèle ci-dessus est complètement équivalent à

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

Capture d'appels de sous-programmes

Une mise en garde pour Perl et PCRE: si le groupe 1dans les exemples ci-dessus contient d'autres groupes, les appels de sous-programmes ne se souviendront pas de leurs captures. Considérons cet exemple:

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

Cela ne correspondra pas

x1:1 y2:2 z3:3

car après le retour des appels de sous-routines, la nouvelle capture du groupe 2est ignorée. Au lieu de cela, ce modèle correspond à cette chaîne:

x1:1 y2:1 z3:1

Ceci est différent de Ruby, où les appels de sous - programme font garder leurs captures, de sorte que le Ruby regex équivalent (\w(\d):)\2 \g<1>\2 \g<1>\2correspondrait à la première des exemples ci - dessus.

Martin Ender
la source
Vous pouvez utiliser \1pour Javascript. Et PHP aussi (je suppose).
Ismael Miguel
5
@IsmaelMiguel Ceci n'est pas une référence en arrière. Cela évalue à nouveau le motif. Par exemple (..)\1correspondrait ababmais échouerait abbaalors que (..)(?1)correspondrait à ce dernier. C'est en fait un appel de sous-programme dans le sens où l'expression est appliquée à nouveau, au lieu de correspondre littéralement à ce à quoi elle correspondait la dernière fois.
Martin Ender
Wow, je n'en avais aucune idée! Apprendre quelque chose de nouveau tous les jours
Ismael Miguel
En .NET (ou d'autres saveurs sans cette fonctionnalité):(?=a.b.c)(.[0_B!$]){3}d
jimmy23013
@ user23013 qui semble très spécifique à cet exemple particulier. Je ne suis pas sûr que ce soit applicable si je réutilise un sous-modèle donné sous différentes formes.
Martin Ender
9

Causer un match pour échouer

Lorsque vous utilisez regex pour résoudre des problèmes de calcul ou pour faire correspondre des langages extrêmement non normaux, il est parfois nécessaire de faire échouer une branche du motif, quel que soit votre emplacement dans la chaîne. L’approche naïve consiste à utiliser un regard négatif négatif:

(?!)

Le contenu (le motif vide) correspond toujours, de sorte que la perspective négative échoue toujours. Mais le plus souvent, il existe une option beaucoup plus simple: il suffit d'utiliser un caractère dont vous savez qu'il n'apparaîtra jamais dans l'entrée. Par exemple, si vous savez que votre entrée sera toujours composée uniquement de chiffres, vous pouvez simplement utiliser

!

ou tout autre caractère non numérique, non méta, susceptible de provoquer un échec.

Même si votre entrée peut potentiellement contenir des sous-chaînes, il existe des méthodes plus courtes que (?!). Toute saveur permettant aux ancres d'apparaître dans un motif, par opposition à la fin, pourrait utiliser l'une des solutions à 2 caractères suivantes:

a^
$a

A noter cependant que certaines saveurs traiteront ^et $comme des caractères dans ces positions, parce qu'ils ne font pas réellement évidemment sens comme points d' ancrage.

Dans la version ECMAScript, il existe également la solution plutôt élégante à deux caractères

[]

Il s'agit d'une classe de caractères vide, qui essaie de s'assurer que les prochains caractères est l'un de ceux de la classe - mais il n'y a pas de caractères dans la classe, donc cela échoue toujours. Notez que cela ne fonctionnera sous aucune autre forme, car les classes de caractères ne peuvent généralement pas être vides.

Martin Ender
la source
8

Optimisez vous OU

Chaque fois que vous avez 3 alternatives ou plus dans votre RegEx:

/aliceblue|antiquewhite|aquamarine|azure/

Vérifiez s'il y a un début commun:

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

Et peut-être même une fin commune?

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

Remarque: 3 n'est que le début et prendrait en compte la même longueur, 4+ ferait une différence


Mais que se passe-t-il s'ils n'ont pas tous un préfixe commun? (les espaces ne sont ajoutés que par souci de clarté)

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

Regroupez-les, tant que la règle des 3 ans et plus est logique:

/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 même généraliser si l'entropie satisfait votre cas d'utilisation:

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

^ dans ce cas, nous sommes sûrs que nous ne recevons cluenicrown slack Ryan

Cela "selon certains tests" améliore également les performances, car il fournit un point d' ancrage pour commencer.

CSᵠ
la source
1
Si le début ou la fin commun dépasse un caractère, même le regroupement de deux peut faire la différence. Comme aqua|aquamarineaqua(|marine)ou aqua(marine)?.
Paŭlo Ebermann
6

Celui-ci est assez simple, mais mérite d'être signalé:

Si vous vous trouvez répéter la classe de caractères que [a-zA-Z]vous pouvez probablement utiliser [a-z]et append le i(Case- i modificateur nsensitive) à votre regex.

Par exemple, en Ruby, les deux expressions rationnelles suivantes sont équivalentes:

/[a-zA-Z]+\d{3}[a-zA-Z]+/
/[a-z]+\d{3}[a-z]/i - 7 octets plus court

D'ailleurs, les autres modificateurs peuvent aussi raccourcir votre longueur totale. Au lieu de faire ceci:

/(.|\n)/

qui correspond à TOUT caractère (car le point ne correspond pas à la nouvelle ligne), utilisez le modificateur s ingle-line s, qui fait correspondre les points à la nouvelle ligne.

/./s - 3 octets plus court


Dans Ruby, il existe une tonne de classes de caractères intégrées pour regex. Voir cette page et rechercher "Propriétés du personnage".
Un bon exemple est le "symbole monétaire". Selon Wikipedia, il existe une tonne de symboles monétaires possibles, et les placer dans une classe de caractères coûterait très cher ( [$฿¢₡Ð₫€.....]) alors que vous pouvez les faire correspondre à 6 octets:\p{Sc}

Devon Parsons
la source
1
Sauf JavaScript, où smodificateur n'est pas pris en charge. :( Mais là, vous pouvez utiliser l' /[^]/astuce propriétaire de JavaScript .
manatwork
Notez que (.|\n)cela ne fonctionne même pas dans certaines saveurs, car .souvent aussi ne correspond à aucun autre type de séparateur de ligne. Cependant, la manière habituelle de faire cela (sans s) est d’utiliser [\s\S]les mêmes octets que (.|\n).
Martin Ender
@ MartinBüttner, mon idée était de le garder avec les autres conseils relatifs à la fin de la ligne. Mais si vous pensez que cette réponse concerne davantage les modificateurs, je n’ai aucune objection si vous la republiez.
Manatwork
@manatwork done (et ajouté une astuce liée non spécifique à ES)
Martin Ender
6

Un analyseur de langage simple

Vous pouvez construire un analyseur syntaxique très simple avec un RE tel que \d+|\w+|".*?"|\n|\S. Les jetons que vous devez associer sont séparés par le caractère RE 'ou'.

Chaque fois que le moteur RE essaie de faire correspondre la position actuelle dans le texte, il essaiera le premier motif, puis le second, etc. S'il échoue (sur un caractère d'espacement ici par exemple), il continue et essaie à nouveau les correspondances. . L'ordre est important. Si nous plaçions le \Sterme avant le \d+terme, \Sil apparaitrait en premier sur n'importe quel caractère non spatial qui romprait notre analyseur.

Le ".*?"correcteur de chaînes utilise un modificateur non-glouton, nous ne faisons donc correspondre qu'une chaîne à la fois. Si votre RE n'a pas de fonctions non gloutonnes, vous pouvez utiliser "[^"]*"ce qui est équivalent.

Exemple 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', ':', ']', ')']

Exemple Python Golfé:

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

Vous pouvez ajuster les modèles et leur ordre en fonction de la langue à associer. Cette technique fonctionne bien pour les expressions JSON, HTML de base et numériques. Il a été utilisé avec succès à plusieurs reprises avec Python 2, mais devrait être suffisamment général pour fonctionner dans d'autres environnements.

Logic Knight
la source
6

\K au lieu de lookbehind positif

PCRE et Perl prennent en charge la séquence d'échappement \K, ce qui réinitialise le début du match. Cela ab\Kcdnécessitera que votre chaîne d'entrée contienne, abcdmais la correspondance rapportée sera seulement cd.

Si vous utilisez une recherche positive au début de votre modèle (ce qui est probablement l'endroit le plus probable), dans la plupart des cas, vous pouvez utiliser à la \Kplace et économiser 3 octets:

(?<=abc)def
abc\Kdef

Ceci est équivalent dans la plupart des cas, mais pas entièrement. Les différences apportent à la fois des avantages et des inconvénients:

  • Upside: PCRE et Perl ne supportent pas les recherches arbitraires (seul le .NET le fait). C'est-à-dire que vous ne pouvez pas faire quelque chose comme (?<=ab*). Mais avec \Kvous pouvez mettre n'importe quel motif devant! Alors ça ab*\Kmarche. Cela rend cette technique beaucoup plus puissante dans les cas où elle est applicable.
  • Upside: Lookarounds ne pas revenir en arrière. Ceci est pertinent si vous souhaitez capturer ultérieurement quelque chose dans la recherche, mais il existe plusieurs captures possibles qui conduisent toutes à des correspondances valables. Dans ce cas, le moteur de regex ne tenterait jamais qu'une de ces possibilités. Lorsque vous utilisez \Kcette partie de la regex, vous faites comme si de rien n'était.
  • Inconvénient: Comme vous le savez probablement, plusieurs correspondances d'une expression rationnelle ne peuvent pas se chevaucher. Les méthodes de recherche sont souvent utilisées pour contourner partiellement cette limitation, car elles permettent de valider une partie de la chaîne déjà utilisée par une correspondance antérieure. Donc, si vous voulez faire correspondre tous les caractères qui suivent, ab vous pouvez utiliser (?<=ab).. Compte tenu de l'entrée

    ababc
    

    cela correspondrait à la seconde aet la c. Cela ne peut pas être reproduit avec \K. Si vous utilisiez ab\K., vous n'obtiendrez que le premier match, car à présent, ce abn'est pas un lookaround.

Martin Ender
la source
Si un modèle utilise la \Kséquence d'échappement dans une assertion positive, le début signalé d'une correspondance réussie peut être supérieur à la fin de la correspondance.
Hwnd
@hwnd Mon point est que, étant donné ababc, il n'y a aucun moyen de faire correspondre à la fois la seconde aet la cavec \K. Vous aurez seulement un match.
Martin Ender
Vous avez raison, pas avec la fonctionnalité elle-même. Vous auriez à ancrer avec\G
hwnd
@hwnd Ah je vois ce que tu veux dire maintenant. Mais je suppose qu’à ce stade-là (du point de vue du golf), il est préférable de regarder en arrière, car vous pourriez en avoir même besoin de toute façon, car vous ne pouvez pas être sûr que .le dernier match a été un a.
Martin Ender
1
Utilisation intéressante de \ K =)
hwnd
5

Faire correspondre n'importe quel personnage

La variante ECMAScript manque des smodificateurs, ce qui fait .correspondre tout caractère (y compris les nouvelles lignes). Cela signifie qu'il n'y a pas de solution à un seul caractère pour faire correspondre des caractères complètement arbitraires. La solution standard dans d'autres versions (quand on ne veut pas utiliser spour une raison quelconque) est [\s\S]. Cependant, ECMAScript est la seule saveur (à ma connaissance) qui prend en charge les classes de caractères vides, et a donc une alternative beaucoup plus courte: [^]. Il s'agit d'une classe de caractères vides refusée - c'est-à-dire qu'elle correspond à n'importe quel caractère.

Même pour d’autres saveurs, nous pouvons tirer des enseignements de cette technique: si nous ne voulons pas utiliser s(par exemple, parce que nous avons toujours besoin de la signification habituelle de .ailleurs), il peut toujours exister un moyen plus court de faire correspondre les caractères de nouvelle ligne et les caractères imprimables, à condition qu'il y ait un caractère que nous savons n'apparaît pas dans l'entrée. Disons que nous traitons des nombres délimités par des nouvelles lignes. Ensuite, nous pouvons associer n'importe quel caractère [^!], car nous savons que !cela ne fera jamais partie de la chaîne. Cela enregistre deux octets sur le naïf [\s\S]ou [\d\n].

Martin Ender
la source
4
En Perl, cela \Nsignifie exactement ce que .signifie en dehors du /smode, sauf que ce mode n'est pas affecté par un mode.
Konrad Borowski
4

Utiliser des groupes atomiques et des quantificateurs possessifs

J'ai trouvé des groupes atomiques ( (?>...)) et quantificateurs possessifs ( ?+, *+, ++, {m,n}+) parfois très utiles pour jouer au golf. Il correspond à une chaîne et interdit les retours en arrière plus tard. Ainsi, il ne fera correspondre que la première chaîne pouvant être lue que le moteur de regex trouve.

Par exemple: Pour faire correspondre une chaîne avec un nombre impair de a'au début, qui n'est pas suivi de plusieurs a', vous pouvez utiliser:

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

Cela vous permet d'utiliser des éléments comme .*librement, et s'il y a une correspondance évidente, il n'y aura pas d'autre possibilité de faire correspondre trop ou trop peu de caractères, ce qui pourrait briser votre modèle.

Dans regex .NET (qui n'a pas de quantificateurs possessifs), vous pouvez l'utiliser pour faire apparaître le plus grand multiple du groupe 1 sur 3 (avec un maximum de 30) (sans très bien jouer au golf):

(?>((?<-1>){3}|){10})
jimmy23013
la source
1
ECMAscript manque également des quantificateurs possessifs ou des groupes atomiques :(
CSᵠ
4

Oublier un groupe capturé après une sous-expression (PCRE)

Pour cette regex:

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

Si vous souhaitez effacer le \ 2 après le groupe 1, vous pouvez utiliser la récursivité:

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

Il s'agira aaalors que le précédent ne le sera pas. Parfois, vous pouvez également utiliser ??ou même ?à la place de {0}.

Cela peut être utile si vous utilisez beaucoup de récursions et que certaines des références arrières ou des groupes conditionnels apparaissent à différents endroits de votre expression régulière.

Notez également que les groupes atomiques sont supposés être des récurrences dans PCRE. Donc, cela ne correspondra pas à une seule lettre a:

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

Je ne l'ai pas encore essayé dans d'autres saveurs.

Pour les têtes d’attente, vous pouvez également utiliser des doubles négatifs à cette fin:

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

Expressions facultatives

Il est parfois utile de se rappeler que

(abc)?

est la plupart du temps identique à

(abc|)

Il y a cependant une petite différence: dans le premier cas, le groupe capture abcou ne capture pas du tout. Ce dernier cas ferait échouer une référence arrière inconditionnellement. Dans la deuxième expression, le groupe capturera abcou une chaîne vide, où le dernier cas fera une correspondance de référence arrière sans condition. Pour émuler ce dernier comportement avec ?vous, vous devez tout entourer d'un autre groupe, ce qui coûterait deux octets:

((abc)?)

La version using |est également utile lorsque vous souhaitez néanmoins insérer l'expression dans une autre forme de groupe sans vous soucier de la capture:

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

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

Enfin, cette astuce peut également être appliquée à ungreedy ?où il enregistre un octet même sous sa forme brute (et par conséquent 3 octets lorsqu'il est combiné avec d'autres formes de groupes):

(abc)??
(|abc)
Martin Ender
la source
1

Plusieurs lookaheads qui correspondent toujours (.NET)

Si vous avez 3 constructions lookahead ou plus qui correspondent toujours (pour capturer des sous-expressions), ou s'il existe un quantificateur sur un lookahead suivi de quelque chose d'autre, elles doivent donc être dans un groupe pas nécessairement capturé:

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

Ceux-ci sont plus courts:

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

ane devrait pas être le nom d'un groupe capturé. Vous ne pouvez pas utiliser |pour signifier la chose habituelle bet csans ajouter une autre paire de parenthèses.

Malheureusement, les groupes d'équilibrage dans les conditionnels semblaient difficiles, ce qui le rendait souvent inutile.

jimmy23013
la source