IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage Perl Discussion :

Regex - Utilisation quantificateur non gourmand


Sujet :

Langage Perl

  1. #1
    Membre confirmé
    Homme Profil pro
    Ingénieur intégration
    Inscrit en
    Mars 2015
    Messages
    138
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Morbihan (Bretagne)

    Informations professionnelles :
    Activité : Ingénieur intégration
    Secteur : Service public

    Informations forums :
    Inscription : Mars 2015
    Messages : 138
    Par défaut Regex - Utilisation quantificateur non gourmand
    Bonjour,

    j'ai en entrée la chaîne de caractères :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    ressources=PRD_PenPenAXVI13 <> A TRAITER [liberation==oui]
    et souhaite extraire le nom de la ressource, ici PRD_PenPenAXVI13

    J'y arrive avec le code suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    if ( $str =~ /^ ([^\s]+) \s+ .+ $/x ) {
        push @{$r_h->{$env}->{'ressources'}->{$1}->{'jobs'}}, $job;
    }
    Par contre, je dois bugger sur un truc, je n'y arrive pas en utilisant les quantificateurs non gourmands !
    J'ai essayé /^ (.+) \s+? .+ $/x et diverses variantes pas meilleures comme /^ ((.+) \s)+? .+ $/x

    Si quelqu'un peut m'éclairer, parce que je tourne en rond sur ce point.

    Merci
    --
    Patrick

  2. #2
    Expert confirmé Avatar de disedorgue
    Homme Profil pro
    Ingénieur intégration
    Inscrit en
    Décembre 2012
    Messages
    4 376
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur intégration
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Décembre 2012
    Messages : 4 376
    Par défaut
    Pour que l'on puisse te donner une réponse correcte à ton besoin, il faudrait que tu nous dises ce que tu veux faire au juste, car tes regex ne semble pas du tout faire ce que tu veux, si dans ta chaine tu avais tototiti=tata djdjdj sjsjsj ça fonctionnerait aussi et $1 retournerait tototiti=tata...
    Et en dessous tu lui dis dit que c'est la valeur de ressources que tu veux et même pas sur la même variable...

  3. #3
    Membre confirmé
    Homme Profil pro
    Ingénieur intégration
    Inscrit en
    Mars 2015
    Messages
    138
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Morbihan (Bretagne)

    Informations professionnelles :
    Activité : Ingénieur intégration
    Secteur : Service public

    Informations forums :
    Inscription : Mars 2015
    Messages : 138
    Par défaut
    Désolé, je vais essayer de reformuler.

    L'entrée complète, listant les différentes ressources séparées par une virgule, est la suivante :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    ressources=PRD_PenPenAXVI13 <> A TRAITER [liberation==oui],PRD_PenPenAXVI31 = CFT CNTDF1 RECU [liberation==oui],PRD_PenPenAXVI32 = CFT CNTDF2 RECU [liberation==oui]
    Le code mis à jour pour cette partie est :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    } elsif ( $record =~ /^ ressources= (.+) $/x ) {
         my @rsc = split /,/, $1;    # @rsc = ('PRD_PenPenAXVI13 <> A TRAITER [liberation==oui]' , 'PRD_PenPenAXVI31 = CFT CNTDF1 RECU [liberation==oui]' , 'PRD_PenPenAXVI32 = CFT CNTDF2 RECU [liberation==oui]')
     
         foreach my $str ( @rsc ) {
             if ( $str =~ /^ ([^\s]+) \s+ .+ $/x ) {
                 say $1;  # affiche  PRD_PenPenAXVI13 puis PRD_PenPenAXVI31 et enfin PRD_PenPenAXVI32
             }
        }
    }

    Mon besoin est donc d'extraire le nom de chaque ressource de ma chaîne de caractères et de l'afficher (say $1).

    J'y arrive avec l'expression régulière $str =~ /^ ([^\s]+) \s+ .+ $/x, mais cette solution ne me satisfait qu'à moitié, et je souhaiterai y arriver avec une expression régulière utilisant un quantificateur non gourmand

  4. #4
    Expert confirmé Avatar de disedorgue
    Homme Profil pro
    Ingénieur intégration
    Inscrit en
    Décembre 2012
    Messages
    4 376
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur intégration
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Décembre 2012
    Messages : 4 376
    Par défaut
    Ok, dans ce cas, interprétons tes regex:
    Sans le connecteur x, celle-ci est équivalente à:
    ^ ==> comme il est situer en début de regex, c'est pour dire "début de chaine"
    ([^\s]+) ==> au moins un caractère différent d'un type espace que l'on sauvegarde dans un argument (ici $1, puisque c'est le premier).
    \s+ ==> au moins un type d'espace
    .+$ ==> au moins quelque chose une fois.
    Tout ceci peut se traduire par prendre une chaine ne commençant pas par un type espace que l'on sauvegardera dans l'argument 1 si celle ci est suivant au moins d'un type espace + au moins un caractère quelconque avant de se terminer.

    Avec la suppression de gourmandise, il faut le penser légèrement différemment:
    prendre toute la chaine du début jusqu'au premier type espace non compris que l'on sauvegardera dans l'argument 1 si celle ci est suivant au moins d'un type espace + au moins un caractère quelconque avant de se terminer.
    ce qui donne:
    et si on rajoute le connecteur x, elle peut s'écrire:
    Ici, on utilise un '*' au lieu du '+' pour ne pas prendre en compte un espace en début de chaine (en gros, avec le '+' la suppression de la gourmandise commence à +1 alors qu'avec le '*' elle commence à +0.

  5. #5
    Membre confirmé
    Homme Profil pro
    Ingénieur intégration
    Inscrit en
    Mars 2015
    Messages
    138
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Morbihan (Bretagne)

    Informations professionnelles :
    Activité : Ingénieur intégration
    Secteur : Service public

    Informations forums :
    Inscription : Mars 2015
    Messages : 138
    Par défaut
    Merci, je teste cela demain et te tiens au courant.

  6. #6
    Rédacteur/Modérateur

    Avatar de Lolo78
    Homme Profil pro
    Conseil - Consultant en systèmes d'information
    Inscrit en
    Mai 2012
    Messages
    3 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Conseil - Consultant en systèmes d'information
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Mai 2012
    Messages : 3 612
    Billets dans le blog
    1
    Par défaut
    Bonsoir Patrick,

    je fais remarquer qu'avec ta première regex (quantificateurs avides ou gourmands), tu ne récupères pas le seul nom de la ressource (PRD_PenPenAXVI13) comme tu le dis dans le texte du post, mais la chaîne de caractères plus large du genre ressources=<nom_ressource>, comme on peut le voir dans ce test sous le debugger Perl.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
      DB<1> $str = 'ressources=PRD_PenPenAXVI13 <> A TRAITER [liberation==oui]';
     
      DB<2> print $1 if $str =~ /^ ([^\s]+) \s+ .+ $/x
    ressources=PRD_PenPenAXVI13
    Le motif /^ ([^\s]+) \s+ .+ $/x capture tous les caractères autres que des espaces depuis le début de la chaîne jusqu'au(x) premier(s) caractère(s) de type espace (à condition cependant qu'il y ait encore au moins un caractère autre qu'un espace après le ou les espaces détectés par \s+ ).

    En fait, compte tenu de ce que tu cherches à faire et de la chaîne de caractères à analyser (et sachant qu'un motif n'a pas besoin de reconnaître toute la chaîne pour réussir, il suffit en sens inverse que le motif soit entièrement utilisé dans la reconnaissance), je pense que la seconde partie de la regex ne sert probablement à rien et que le motif /^([^\s]+)/ serait suffisant pour tes besoins:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
      DB<3>  print $1 if $str =~ /^([^\s]+)/;     # Ou: /^ ([^\s]+)/x si tu préfères
    ressources=PRD_PenPenAXVI13
    Si tu veux absolument utiliser un quantificateur frugal ou non gourmand (je ne vois trop pourquoi, mais, bon, pourquoi pas?), alors on peut supposer que tu désires capturer tous les caractères depuis le début de la ligne jusqu'au premier espace, ce qui peut se traduire par le motif /^ (.+?) \s /x. Exemple sous le debugger:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
      DB<4> print $1 if $str =~ /^ (.+?)  \s /x;
    ressources=PRD_PenPenAXVI13
      DB<5>

  7. #7
    Expert confirmé Avatar de disedorgue
    Homme Profil pro
    Ingénieur intégration
    Inscrit en
    Décembre 2012
    Messages
    4 376
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur intégration
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Décembre 2012
    Messages : 4 376
    Par défaut
    Attention, comme je le disais, /^([^\s]+)/ n'est pas équivalent à /^ (.+?) \s /x, suffit de tester avec une chaine commençant par un espace...

  8. #8
    Membre confirmé
    Homme Profil pro
    Ingénieur intégration
    Inscrit en
    Mars 2015
    Messages
    138
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Morbihan (Bretagne)

    Informations professionnelles :
    Activité : Ingénieur intégration
    Secteur : Service public

    Informations forums :
    Inscription : Mars 2015
    Messages : 138
    Par défaut
    Merci à vous deux pour les bons conseils, je dois me forcer à quitter le clavier quand il le faut et prendre le stylo et la feuille, bien plus efficaces.

    Ma demande concernant les quantificateurs gourmands était due à l'envie d'apprendre à mieux les utiliser et aussi au fait qu'il me semblait plus judicieux de les utiliser en place et lieu d'un motif d'exclusion ([^\s+])

    Mon code est maintenant parfaitement fonctionnel et j'ai deux solutions répondant à ma demande :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
                    } elsif ( $record =~ /^ ressources= (.+) $/x ) {
                        my @rsc = split /,/, $1;
                        foreach my $str ( @rsc ) {
                            #if ( $str =~ /^ ([^\s]+) /x ) {   # solution 1
                            if ( $str =~ /^ (.*?) \s /x ) {       # solution 2
                                push @{$r_h->{$env}->{'ressources'}->{$1}->{'apps'}}, $app;
                            }
                        }
                    }

  9. #9
    Expert confirmé Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 986
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Justicier interdimensionnel

    Informations forums :
    Inscription : Mars 2009
    Messages : 2 986
    Par défaut
    Citation Envoyé par ptonnerre Voir le message
    il me semblait plus judicieux de les utiliser en place et lieu d'un motif d'exclusion ([^\s+])
    Judicieux je ne sais pas mais par contre le point auquel on applique un quantificateur non-gourmand nécessitera plus de tests que [^\s]+.
    Il faut bien voir comment marche un quantificateur non-gourmand: pour chaque caractère consommé par le quantificateur, le moteur de regex doit vérifier si oui ou non la suite de la pattern matche la chaîne; et ce jusqu'à ce que la pattern réussisse.
    Alors qu'avec [^\s]+, le moteur de regex n'a pas à se poser de questions, il consomme tous les caractères qui ne sont pas des espaces, et seulement ensuite il s'occupe du reste de la pattern (le cas échéant).

    Aussi comme souligné précédemment, il va bien falloir te débarrasser du ressources= à un moment ou à un autre. Moi je la jouerai comme ça:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    } elsif ( $record =~ s/^ressources=/,/ ) { # d'un pierre deux coups, tu testes et élimines le "ressources="
        while ( $record =~ /,(\S+)/g ) { # à mon avis \w+ serait encore mieux, comme ça pas de mauvaises surprise s'il n'y a pas d'espace à l'endroit prévu
            push @{$r_h->{$env}->{'ressources'}->{$1}->{'apps'}}, $app;
        }
    }

  10. #10
    Membre confirmé
    Homme Profil pro
    Ingénieur intégration
    Inscrit en
    Mars 2015
    Messages
    138
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Morbihan (Bretagne)

    Informations professionnelles :
    Activité : Ingénieur intégration
    Secteur : Service public

    Informations forums :
    Inscription : Mars 2015
    Messages : 138
    Par défaut
    Merci CosmoKnacki pour les conseils, j'en prends bonne note.

    Citation Envoyé par CosmoKnacki Voir le message
    Aussi comme souligné précédemment, il va bien falloir te débarrasser du ressources=
    Je le gérai dans le code, jette un oeil sur ma précédente réponse.


  11. #11
    Expert confirmé Avatar de disedorgue
    Homme Profil pro
    Ingénieur intégration
    Inscrit en
    Décembre 2012
    Messages
    4 376
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur intégration
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Décembre 2012
    Messages : 4 376
    Par défaut
    On ne voit pas vraiment de différence si on benchmark:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    #!/usr/bin/perl
    use Benchmark;
     
    $count = shift || die "Compteur SVP!\n";
     
      $words = "ressources=PRD_PenPenAXVI13 <> A TRAITER [liberation==oui]";
     
    sub One {
    	for (1..1000){
    		$word =~ /^ ([^\s]+) /x;
    	}
     
    }
     
     
    sub Two {
    	for (1..1000){
    		$word =~ /^ (.*?) \s /x;
    	}
    }
     
    sub Three {
    	for (1..1000){
    		$word =~ /^(\S+)/x;
    	}
    }
    timethese (
      $count,
      {'Method A:[^\s]+' => '&One',
       'Method B:(.*?)\s' => '&Two',
       'Method C:(\S+)' => '&Three'}
    );
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    Benchmark: timing 100000 iterations of Method A:[^\s]+, Method B:(.*?)\s, Method C:(\S+)...
    Method A:[^\s]+: 14 wallclock secs (14.11 usr +  0.03 sys = 14.14 CPU) @ 7072.14/s (n=100000)
    Method B:(.*?)\s: 15 wallclock secs (14.33 usr +  0.03 sys = 14.36 CPU) @ 6963.79/s (n=100000)
    Method C:(\S+): 14 wallclock secs (14.12 usr +  0.03 sys = 14.15 CPU) @ 7067.14/s (n=100000)
    Mais la méthode qui stop la gourmandise a surtout été faite pour des cas de motifs sur plusieurs caractères qui se répète et non pour des motifs d'un seul caractère comme c'est le cas ici.

  12. #12
    Expert confirmé Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 986
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Justicier interdimensionnel

    Informations forums :
    Inscription : Mars 2009
    Messages : 2 986
    Par défaut
    J'ai lancé plusieurs fois ton benchmark et il n'en ressort rien de vraiment significatif (les patterns 1 et 3 ont plus fréquemment de meilleurs performances, mais sans plus, rien de criant), car les patterns sont trop simples, ancrées, la chaîne pas bien longue, et que le système n'a pas que ça à foutre. Néanmoins, ça ne justifie absolument pas d'utiliser un quantificateur non gourmand là où ça défie le bon sens (étant entendu de connaître le comportement de ces quantificateurs).

    Ceci étant dit, au delà du fonctionnement basique des moteurs de regex à backtracking, certains (la plupart?) sont "munis" d'une phase d'analyse (appelée "transmission" par J.Friedl) plus ou moins poussée qui a pour but de pallier aux faiblesses architecturales du moteur même (la plus répandue quelque soit le moteur, consiste à sélectionner au préalable les positions dans la chaîne où la pattern peut éventuellement réussir lorsque la dite pattern démarre par une chaîne littérale. Dans ce cas les positions "intéressantes" sont sélectionnées au préalable avec un algorithme rapide comme celui d'Aho-Corasick.). Aussi, le module Python regex, va pousser l'assistanat au développeur jusqu'à lui pardonner des constructions pathologiques (par je ne sais quelle magie, mais probablement en exploitant cette même phase) qui dans n'importe quelle autre langage aboutiraient à un catastrophic backtracking (ce qui est amusant dans l'histoire, c'est que cette fonctionnalité a juste été présentée comme un bug à résoudre, puis enfin résolu au fil des versions. Donc avec ce module on peut écrire n'importe quelle merde, ça passe.). Tout ça pour dire, que si ça se trouve, il y a une optimisation de derrière les fagots en Perl (qui est pionnier sur le sujet) avant la marche "normale" du moteur de regex qui explique ces résultats peu probants.

    Mais quoi qu'il en soit, avec un minimum de compréhension et de bon sens, on sait quoi écrire.

  13. #13
    Rédacteur/Modérateur

    Avatar de Lolo78
    Homme Profil pro
    Conseil - Consultant en systèmes d'information
    Inscrit en
    Mai 2012
    Messages
    3 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Conseil - Consultant en systèmes d'information
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Mai 2012
    Messages : 3 612
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par disedorgue Voir le message
    Attention, comme je le disais, /^([^\s]+)/ n'est pas équivalent à /^ (.+?) \s /x, suffit de tester avec une chaine commençant par un espace...
    Je suis bien d'accord, mais mon objectif n'était pas d'écrire un motif équivalent (le motif d'origine n'était à mon avis pas vraiment optimal de toute façon, et j'ai dit en partie pourquoi) mais bien d 'écrire un motif frugal permettant de résoudre simplement le cas pratique présenté.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 2
    Dernier message: 08/02/2015, 21h48
  2. Quelle expression Regex utiliser?
    Par l_informaticien dans le forum Général Java
    Réponses: 5
    Dernier message: 03/05/2013, 14h09
  3. Réponses: 2
    Dernier message: 18/09/2008, 10h49
  4. Regex: utilisation bbcode
    Par D_ident_1 dans le forum Général JavaScript
    Réponses: 1
    Dernier message: 25/07/2007, 00h00
  5. Utilisation de regex : TRegExpr
    Par trakiss dans le forum Langage
    Réponses: 2
    Dernier message: 22/06/2005, 23h07

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo