Aller au contenu

Le grand jeu des RegExp


Yangzebul

Messages recommandés

Bonjour,

Je vous propose un petit exercice de style et cérébral, à base de ce petit langage de programmation omniprésent et que l'on aime tant : :love:les expressions régulières :love:

Si si vous savez ce petit DSL "read only" qui permet de faire en une vingtaine de caractère ce qu'on ferait en 150 lignes de code ? Celui qu'on aime tant écrire car quand on en as une bien cossue qui marche ça impose le respect ; mais qu'on déteste tant trouver quand ce n'est pas la notre car sauf si l'on est passionné / amateur de perl ou brainfuck / pas contre perdre une demi heure de sa vie, on va galérer à la comprendre.

Leur puissance et leur utilité sont souvent insoupçonnées des débutants. Je vous propose donc quelques petits défis pour le fun et la gloire. 8)

Les règles :

- même si une expression réponds au specs mentionnées elle est fausse si elle ne se comporte pas correctement avec une entrée non valide quelconque

- chaque défi proposé devra indiquer si l'expression doit être pratique (transformation de texte), validante et / ou analytique

- une expression qui gère mal ses groupes de captures (çàd : capturant des données inutiles, illogiques, redondantes)

- une expression avec des groupes / classes dupliqués est moins bonne qu'une expression sans

- une expression courte est meilleure qu'une expression équivalente plus longue

- tout le monde peut proposer un défi à n'importe quel moment, seulement s'il est capable de pouvoir lui même le résoudre

- chaque réponse devra être "copier-coller" compatible, ex :

"abcdef".match(/abc/);
// ['abc']

- un exemple et une description raisonnablement précise du comportement attendu doit être donné pour chaque problème

- chaque exemple doit pouvoir tourner dans la console de firebug pour que cela reste fun et facile à tester en même temps que l'on lit le forum, donc pas de trucs du genre look-behind, groupes atomiques, groupes nommés et cie.

Allez j'ouvre le bal :

1 - Formatage de nombres :

Ecrire une expression telle que

100000 soit transformé en 1 000 000

Master level : gérer les nombres décimaux !

2 - IPV4 (le roi est mort vive le roi !)

Ecrire une expression validant une adresse IP telle que : "XXX.XXX.XXX.XXX"

où X de 0 à 255, avec ou sans zéros signifiants (çàd autant 019 que 19 ou 003 que 3)

Ecrire la même expression mais cette fois çi analytique, donc pouvant extraire chacun des groupes ! Peu importe si vous extrayez les zéros initaux ou non, de toute façon ça n'a d'intérêt que pour les caster en int par la suite.

Master level, les deux même mais avec un masque de sous réseau tel que "XXX.XXX.XXX.XXX /MM"

3 - Capitalisation

Ecrire une expression permettant de capitaliser un nom prénom tel que : "pHiliPPE riSolI" => "Philippe Risoli"

Master level, Capitaliser les noms et prénoms compoosés, et laisser les particules en bas de casse

Bonne chance !

(*) lots dans la limite des stocks disponibles (entre 0 positif et zéro négatif)...

Lien vers le commentaire
Partager sur d’autres sites

Pas trop de succès pour l'instant...

Pas le choix alors, je me lance alors !

Pour le problème numéro 1 : /(\d)(?=(?:\d{3})+(?:$))/g

'1234567890'.replace(/(\d)(?=(?:\d{3})+(?:$))/g, '$1 ');
// > "1 234 567 890"
'123456789'.replace(/(\d)(?=(?:\d{3})+(?:$))/g, '$1 ');
// > "123 456 789"
'12345678'.replace(/(\d)(?=(?:\d{3})+(?:$))/g, '$1 ');
// > "12 345 678"

Celle pour les nombres décimaux est plus compliqué mais elle réutilise la même base pour la partie entière du nombre.

Lien vers le commentaire
Partager sur d’autres sites

Oh cool ! C'est rigolo ça (:transpi:)

Je vais essayer la 2, ici sans capture groups :

/^((\d|\d{2}|[0-1]\d{2}|2[0-4]\d|25[0-6])\.){3}(\d|\d{2}|[0-1]\d{2}|2[0-4]\d|25[0-6])$/g

Pour l'instant j'ai pas trouvé plus simple :transpi:

'192.168.0.01'.match(/^(?:(\d|\d{2}|[0-1]\d{2}|2[0-4]\d|25[0-6])\.){3}(\d|\d{2}|[0-1]\d{2}|2[0-4]\d|25[0-6])$/g)
// > ["192.168.0.01"]

'192.000.199.256'.match(/^(?:(\d|\d{2}|[0-1]\d{2}|2[0-4]\d|25[0-6])\.){3}(\d|\d{2}|[0-1]\d{2}|2[0-4]\d|25[0-6])$/g)
// > ["192.000.199.256"]

'192.000.199.257'.match(/^(?:(\d|\d{2}|[0-1]\d{2}|2[0-4]\d|25[0-6])\.){3}(\d|\d{2}|[0-1]\d{2}|2[0-4]\d|25[0-6])$/g)
// > null

Pour utiliser la capture en javascript, en l'absence de groupes nommés, j'ai rien trouvé de mieux que de mettre explicitement tous les groupes de captures identiques :/

/^(\d|\d{2}|[0-1]\d{2}|2[0-4]\d|25[0-6])\.(\d|\d{2}|[0-1]\d{2}|2[0-4]\d|25[0-6])\.(\d|\d{2}|[0-1]\d{2}|2[0-4]\d|25[0-6])\.(\d|\d{2}|[0-1]\d{2}|2[0-4]\d|25[0-6])$/

C'est pas très beau mais bon au moins il me semble que ça fonctionne pas trop mal

'192.000.199.256'.match(/^(\d|\d{2}|[0-1]\d{2}|2[0-4]\d|25[0-6])\.(\d|\d{2}|[0-1]\d{2}|2[0-4]\d|25[0-6])\.(\d|\d{2}|[0-1]\d{2}|2[0-4]\d|25[0-6])\.(\d|\d{2}|[0-1]\d{2}|2[0-4]\d|25[0-6])$/)
// > ["192.000.199.256", "192", "000", "199", "256"]

Lien vers le commentaire
Partager sur d’autres sites

Ouf ! je pensais être le seul masochiste à aimer ça sur ce forum. :transpi:

Il y a quelque chose que je ne comprends pas trop dans ton groupe de capture, tu accepte les nombres de 0 à 256 inclus, pourtant il ne me semble pas que 256 soit une valeur valide.

Si tu veux j'ai plus court pour le groupe de capture

(25[0-5]|2[0-4]\d|[01]?\d\d?) // 29 chars
(\d|\d{2}|[0-1]\d{2}|2[0-4]\d|25[0-6]) // 38 chars

Globalement c'est la même chose sauf que tu exprimes ces 3 alternatives "\d|\d{2}|[0-1]\d{2}" en une seule "[01]?\d\d?" grace au quantifier "?"

Sinon pour l'expression non capturante tu peux faire plus court en mettant une alternative sur le séparateur qui est soit le point, soit la fin de l'input :

/^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)(?:\.|$)){4}/ // 49 chars
/^(?:(\d|\d{2}|[0-1]\d{2}|2[0-4]\d|25[0-6])\.){3}(\d|\d{2}|[0-1]\d{2}|2[0-4]\d|25[0-6])$/g // 90 chars

'255.002.03.4'.match(/^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)(?:\.|$)){4}/) 
>> ["255.002.03.4"]

Pour ce qui est de l'expression capturante, à ma connaissance il n'y a pas d'autre moyen que de dupliquer les groupes :craint:

Donc hormis l'optimisation sur le groupe de capture je n'ai pas mieux à proposer que toi.

Motivé pour proposer un problème de ton crû ? :yes:

Lien vers le commentaire
Partager sur d’autres sites

Il y a quelque chose que je ne comprends pas trop dans ton groupe de capture, tu accepte les nombres de 0 à 256 inclus, pourtant il ne me semble pas que 256 soit une valeur valide.

Ah oui, c'est juste qu'il commençait à être tard :sucre:

Sinon pour l'expression non capturante tu peux faire plus court en mettant une alternative sur le séparateur qui est soit le point, soit la fin de l'input :

/^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)(?:\.|$)){4}/ // 49 chars

Pas tout à fait d'accord :] à cause de ce cas de test :

'192.0.0.1.OH HAI'.match(/^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)(?:\.|$)){4}/)
// ["192.0.0.1."] // Le quatrième point ouvre la porte à toutes les fenêtres

Sinon en effet bien vu pour le raccourci des valeurs de 0 à 199

Motivé pour proposer un problème de ton crû ? :yes:

Heu je vais y réfléchir :transpi: en attendant j'en ferais peut-être une autre des tiennes un de ces jours :)

Lien vers le commentaire
Partager sur d’autres sites

Pas tout à fait d'accord :] à cause de ce cas de test :

'192.0.0.1.OH HAI'.match(/^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)(?:\.|$)){4}/)
// ["192.0.0.1."] // Le quatrième point ouvre la porte à toutes les fenêtres

Bien vu !

Avec 7 caractères de plus ça va mieux :

'255.002.03.4'.match(/^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)(?:\.(?=\d)|$)){4}$/)
// '255.002.03.4'
'255.002.03.4.'.match(/^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)(?:\.(?=\d)|$)){4}$/)
// null

Lien vers le commentaire
Partager sur d’autres sites

  • 3 mois après...

Rhaa, j'en ai chié :

'jOE DAngEr'.replace(/^([a-z])([a-z]+) ([a-z])([a-z]+)$/gi, function(g0,g1,g2,g3,g4, i, s){return g1.toUpperCase()+g2.toLowerCase()+' '+g3.toUpperCase()+g4.toLowerCase();})

//"Joe Danger"

Et en plus c'est moche...

Enfin, ce qui m'embête, c'est le javascript au fond, mais je sasi pas si c'est possible en regexp seulement ce truc

J'ai tenté un peu le mode expert de ce problème : :pleure::pleure:+ :sm:+

:simple:

Mais je n'abandonne pas :D

Lien vers le commentaire
Partager sur d’autres sites

Pas mal, pas mal !

Tu peux améliorer un poil l'expression en utilisant le meta-caractère \b (word boundary). Cela permet de réduire l'expression, utiliser moins de groupes dupliqués, mais aussi de détecter les autres limites de mot tels que les tirets dans les noms composés ou de gérer les cas avec des noms en plusieurs mots (nom composés, étrangers, etc).

'jOE DAngEr'.replace(/\b([a-z])([a-z]+)/gi,function(a,b,c){return b.toUpperCase()+c.toLowerCase()}); // 100 chars
'jOE DAngEr'.replace(/^([a-z])([a-z]+) ([a-z])([a-z]+)$/gi,function(a,b,c,d,e){return b.toUpperCase()+c.toLowerCase()+' '+d.toUpperCase()+e.toLowerCase()}); // 156 chars

Après en situation réelle, pour simplifier l'expression, la meilleure solution reste encore de ne pas passer par des pures regexp, mais de faire un lowercase sur l'input :

'jOE DAngEr'.toLowerCase().replace(/\b([a-z])/g,function(m){return m.toUpperCase()}); // 85 chars

Sinon pour gérer les particules, je n'ai plus la solution exacte en tête, mais si tu veux un indice, je gratterai un peu du côté des lookahead. ;)

Lien vers le commentaire
Partager sur d’autres sites

Une solution pour les particules, avec et sans le recours à un lowercase. Par contre, comme je ne l'ai pas beaucoup testée, il est possible qu'il y a des optimisations encore possibles (surtout sur la deuxième sans lowercase), et peut être quelques failles avec des entrées exotiques.

'jEAn-chARLEs-EdouARD De gaULLE feLLuPiNi'.toLowerCase().replace(/\b(?=[a-z]{3,})([a-z])/g,function(m){return m.toUpperCase()}); // 128 chars

'jEAn-chARLEs-EdouARD De gaULLE feLLuPiNi'.replace(/\b(?=[a-z]{3,})([a-z])([a-z]+)|([a-z]+)/gi,function(a,b,c,d){return b.toUpperCase()+c.toLowerCase()+d.toLowerCase()}); // 170 chars

EDIT : quelqu'un se sens de lancer de nouveaux défis ? Uzak ?

Lien vers le commentaire
Partager sur d’autres sites

Mmh, j'apprends. Déjà que j'ai dû chercher la signification de ?: (et vas-y chercher ça dans google), me voilà maintenant expert des lookahead, lookbehind et lookdetravers...

Ben maintenant quon a vu les IPv4, pourquoi ne pas tenter les IPv6 ?

Je vous propose de

- 1 : reconnaitre une ipv6 valide.

- 2 : simplifier l'écriture d'un ipv6

wikipedia nous dit :

La notation décimale pointée employée pour les adresses IPv4 (par exemple 172.31.128.1) est abandonnée au profit d'une écriture hexadécimale, où les 8 groupes de 2 octets (soit 16 bits par groupe) sont séparés par un signe deux-points :

2001:0db8:0000:85a3:0000:0000:ac1f:8001

La notation complète ci-dessus comprend exactement 39 caractères.

Il est permis d'omettre de 1 à 3 chiffres zéros non significatifs dans chaque groupe de 4 chiffres hexadécimaux. Ainsi, l'adresse IPv6 ci-dessus est équivalente à :

2001:db8:0:85a3:0:0:ac1f:8001

De plus, une unique suite de un ou plusieurs groupes consécutifs de 16 bits tous nuls peut être omise, en conservant toutefois les signes deux-points de chaque côté de la suite de chiffres omise, c'est-à-dire une paire de deux-points (::). Ainsi, l'adresse IPv6 ci-dessus peut être abrégée en :

2001:db8:0:85a3::ac1f:8001

En revanche l'écriture suivante n'est pas valide

2001:db8::85a3::ac1f:8001

car elle contient plusieurs substitutions (dont les longueurs binaires respectives sont ici ambiguës) : il ne peut exister qu'une seule occurrence de la séquence :: dans la notation d'une adresse IPv6.

L'adresse :: est valide !

Ps : évidemment, je ne sais pas si c'est faisable :transpi:

Pps : 3 - rétablir une ipv6 à sa forme longue de 39 caractères

Lien vers le commentaire
Partager sur d’autres sites

Je crois que je tiens quelque chose de pas trop mal. Je ne sais pas, n'étant pas un expert en ipv6, si ça valide correctement toutes les ipv6, mais à priori ca correspond aux specs que tu as données.

J'ai fait une petite suite de test avec 23 cas et tous réussisent.

Si vous trouvez une erreur ou des améliorations je suis preneur !

/^(?:[\da-f]{1,4}(?::(?!$)|$)){8}$|^(?=.*::)(?:(?:^::(?!.*::))?[\da-f]{1,4}(?::‎|::(?!.*::)|$)){1,8}$|^(?:::[\da-f]{0,4}$)/i

var testipv6 = [
  	['2001:0db8:0000:85a3:0000:0000:ac1f:8001', true], // 0
       ['2001:0db8:0000:85a3:0000:0000:ac1f:8001:', false], // 1 ne peut pas finir par :
       [':2001:0db8:0000:85a3:0000:0000:ac1f:8001', false], // 2 ne peut pas commencer par :
       ['2001:0db8:0000:85a3:0000:0000:ac1f:', false], // 3 ne peut pas finir par : manque un groupe
       ['2001:0db8:0000:85a3:0000:0000:ac1f', false], // 4 manque un groupe
       ['2001:db8:0:85a3:00:000:ac1f:8001', true], // 5
       ['2001:db8:0:85a3:0:0:ac1f:8001', true], // 6
       ['2001:db8:0:85a3::ac1f:8001', true], // 7
       ['2001:db8::85a3::ac1f:8001', false], // 8 deux groupes ommis
       ['::', true], // 9
       ['::1', true], // 10
       ['1::', true], // 11
       ['1:::', false], // 12 trois : consécutifs
       [':::', false], // 13 trois : consécutifs
       [':::1', false], // 14 trois : consécutifs
       ['2001:db8:0:85a3:::ac1f:8001', false], // 15 trois : consécutifs
       ['2001:db8::85a3:::ac1f:8001', false], // 16 trois : consécutifs
       ['2001:db8:::85a3::ac1f:8001', false], // 17 trois : consécutifs
       ['::0db8:0000:85a3:0000:0000:ac1f:8001', true], // 18
       ['::0db8:0000:85a3::ac1f:8001', false], // 19 trois groupes ommis
       [':::0db8:0000:85a3:0000:0000:ac1f:8001', false], // 20 trois : consécutifs
       ['2001:0db8:0000:85a3:0000:0000:ac1f::', true], // 21
       ['2001:0db8:0000:85a3:0000:0000:ac1f:::', false] // 22 trois : consécutifs
];

// current
var regexpipv6 = /^(?:[\da-f]{1,4}(?::(?!$)|$)){8}$|^(?=.*::)(?:(?:^::(?!.*::))?[\da-f]{1,4}(?:::(?!.*::)|$)){1,8}$|^(?:::[\da-f]{0,4}$)/i

var errorCount = 0;

testipv6.forEach(function(ipv6, idx, arr){

   var test = ipv6[0].match(regexpipv6),
       result = test===null && ipv6[1]===false || test!==null && ipv6[1]===true;

   if(result) console.info('test ' + idx + ' succeeded for : ' + ipv6[0]);
   else   	console.warn('test ' + idx + ' failed for : ' + ipv6[0]);

   if(!result) ++errorCount;
});

if(errorCount === 0) console.info('all test succeeded');
else console.warn('failed ' + errorCount + ' test out of ' + testipv6.length);

Lien vers le commentaire
Partager sur d’autres sites

:incline: respect, c'est plutôt court, ça gère pas mal de cas.

Quoi, vous avez dit 'pas mal' ? :transpi:

Et oui, un des trucs que je voyais bien chiant, c'est le 24° test que j'ai rajouté :

['1:2:3:4:5:6:7:8:9', false], // 23 Trop de groupes
['1:2:3::4:5:6:7:8', false] // 24 Trop de groupes

J'ai compris le premier cas, je me penche sur les autres !

Lien vers le commentaire
Partager sur d’autres sites

Je viens de survoler brièvement la rfc5952 et la description de wikipedia comporte pas mal d'erreur.

Donc si quelqu'un tombe par hasard sur l'expression ci-dessus, ne l'utilisez pas pour un projet réel !

Exemple :

De plus, une unique suite de un ou plusieurs groupes consécutifs de 16 bits tous nuls peut être omise, en conservant toutefois les signes deux-points de chaque côté de la suite de chiffres omise, c'est-à-dire une paire de deux-points (::).

La rfc interdit formèlement l'abréviation d'un groupe unique

4.2.2. Handling One 16-Bit 0 Field

The symbol "::" MUST NOT be used to shorten just one 16-bit 0 field.

For example, the representation 2001:db8:0:1:1:1:1:1 is correct, but

2001:db8::1:1:1:1:1 is not correct.

De plus il ne faut pas conserver les deux points "de chaque côté" de la séquence abregée, mais insérer deux points à la place d'une séquence abregée. La différence est subtile mais significative. Si l'on prends les cas de l'adresse non spécifié ou de l'adresse localhost cela devient flagrant :

"0000:0000:0000:0000:0000:0000:0000:0000" => deviendrait "" (string vide) selon wikipedia, hors selon la rfc on doit l'écrire "::"

"0000:0000:0000:0000:0000:0000:0000:0001" => deviendrait ":1" selon wikipedia, hors selon la rfc on doit l'écrire "::1"

Et encore je n'ai lu que deux points de la rfc... :craint:

Lien vers le commentaire
Partager sur d’autres sites

J'ai modifier la quantifier de la deuxième alternative pour gérer ton 25° test. Effectivement je n'avais pas pensé à ce cas de figure.

PS : l'expression ne valide toujours pas les vrais ipv6 spécifiés par la rfc, mais juste la description de wikipedia

var testipv6 = [
     ['2001:0db8:0000:85a3:0000:0000:ac1f:8001', true], // 0
         ['2001:0db8:0000:85a3:0000:0000:ac1f:8001:', false], // 1 ne peut pas finir par :
         [':2001:0db8:0000:85a3:0000:0000:ac1f:8001', false], // 2 ne peut pas commencer par :
         ['2001:0db8:0000:85a3:0000:0000:ac1f:', false], // 3 ne peut pas finir par : manque un groupe
         ['2001:0db8:0000:85a3:0000:0000:ac1f', false], // 4 manque un groupe
         ['2001:db8:0:85a3:00:000:ac1f:8001', true], // 5
         ['2001:db8:0:85a3:0:0:ac1f:8001', true], // 6
         ['2001:db8:0:85a3::ac1f:8001', true], // 7
         ['2001:db8::85a3::ac1f:8001', false], // 8 deux groupes ommis
         ['::', true], // 9
         ['::1', true], // 10
         ['1::', true], // 11
         ['1:::', false], // 12 trois : consécutifs
         [':::', false], // 13 trois : consécutifs
         [':::1', false], // 14 trois : consécutifs
         ['2001:db8:0:85a3:::ac1f:8001', false], // 15 trois : consécutifs
         ['2001:db8::85a3:::ac1f:8001', false], // 16 trois : consécutifs
         ['2001:db8:::85a3::ac1f:8001', false], // 17 trois : consécutifs
         ['::0db8:0000:85a3:0000:0000:ac1f:8001', true], // 18
         ['::0db8:0000:85a3::ac1f:8001', false], // 19 trois groupes ommis
         [':::0db8:0000:85a3:0000:0000:ac1f:8001', false], // 20 trois : consécutifs
         ['2001:0db8:0000:85a3:0000:0000:ac1f::', true], // 21
         ['2001:0db8:0000:85a3:0000:0000:ac1f:::', false], // 22 trois : consécutifs
         ['1:2:3:4:5:6:7:8:9', false], // 23 Trop de groupes
         ['1:2:3::4:5:6:7:8', false] // 24 Trop de groupes
];

var regexpipv6 = /^(?:[\da-f]{1,4}(?::(?!$)|$)){8}$|^(?=.*::)(?:(?:^::(?!.*::))?[\da-f]{1,4}(?:::(?!.*::)|$)){1,7}$|^(?:::[\da-f]{0,4}$)/i

var errorCount = 0;

testipv6.forEach(function(ipv6, idx, arr){

   var test = ipv6[0].match(regexpipv6),
       result = test===null && ipv6[1]===false || test!==null && ipv6[1]===true;

   if(result) console.info('test ' + idx + ' succeeded for : ' + ipv6[0]);
   else   	console.warn('test ' + idx + ' failed for : ' + ipv6[0]);

   if(!result) ++errorCount;
});

if(errorCount === 0) console.info('all test succeeded');
else console.warn('failed ' + errorCount + ' test out of ' + testipv6.length);

Lien vers le commentaire
Partager sur d’autres sites

Il est permis d'omettre de 1 à 3 chiffres zéros non significatifs dans chaque groupe de 4 chiffres hexadécimaux.
4.1. Handling Leading Zeros in a 16-Bit Field

Leading zeros MUST be suppressed. For example, 2001:0db8::0001 is

not acceptable and must be represented as 2001:db8::1. A single 16-

bit 0000 field MUST be represented as 0.

Cette entrée de wikipedia à vraiment besoin d'une relecture de toute urgence... :craint:

Lien vers le commentaire
Partager sur d’autres sites

Bon je suis peut être allé un peu vite en besogne, certains cas cités dans wikipedia sont autorisés dans la RFC 4291.

La RFC 5952 est plus restrictive, mais requiert quand même une rétro compatibilité avec la RFC précédente.

A recommendation for a canonical text representation format of IPv6
  addresses is presented in this section.  The recommendation in this
  document is one that complies fully with [RFC4291], is implemented by
  various operating systems, and is human friendly.  The recommendation
  in this section SHOULD be followed by systems when generating an
  address to be represented as text, but all implementations MUST
  accept and be able to handle any legitimate [RFC4291] format.  It is
  advised that humans also follow these recommendations when spelling
  an address.

Donc la page wikipedia est maladroite et périmée, mais pas totalement erronée car les exemples qu'elle met en avant sont formellement dépréciés mais les systèmes restent compatibles.

Lien vers le commentaire
Partager sur d’autres sites

Archivé

Ce sujet est désormais archivé et ne peut plus recevoir de nouvelles réponses.

×
×
  • Créer...