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 PHP Discussion :

[Optimisation] Vérification motifs dans chaîne


Sujet :

Langage PHP

  1. #1
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2012
    Messages
    63
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2012
    Messages : 63
    Points : 33
    Points
    33
    Par défaut [Optimisation] Vérification motifs dans chaîne
    Bonjour all !

    Je cherche à améliorer un micro algo de rien du tout dont je me sers actuellement. Parce qu'il comporte 2 boucles et dès que les chaîne sont trop longue, ça capote ^^

    Voici la fonction :
    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
    $sentence = "06 00 52 44 10 96 85";
    $keyWords = array('06', '96', '12','44');
     
    fonc($sentence, $keyWords);
     
    function fonc($sentence, $keyWords) {
        // On split la phrase en mots.
        $wordsOfSentence = multiexplode(array(" ", "'"), $sentence);
        // On initialise les mots clefs (motifs) formant le pattern à chercher dans chaque phrase.
        $tableOfKeyWords = $keyWords;
        $counter = 0;
        // Pour chaque mot de la phrase.
        foreach ($wordsOfSentence as $value1) {
            // Pour chaque mot clef.
            foreach ($tableOfKeyWords as $value2) {
                // Si ces deux mots sont égaux.
                if (preg_match('/' . $value2 . '/i', $value1)) {
                    // On incrémente le compteur.
                    $counter++;
                }
            }
        }
        echo $counter . '<br/>';
    }
    Ici le compteur retourne 3 par exemple, et c'est parfait et logique ^^

    On m'avait bien proposé ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    if (count(array_intersect(explode(' ', $string), $tabKey)) >= 3) {
        // ...
    }
    Mais ça implique que le motif recherché doit être rigoureusement le même que l'un des mot de la chaîne.

    Or je voudrais que mon algo marche aussi sur cette exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    $sentence = "0678 00 52 44 10 96 85";
    $keyWords = array('06', '96', '12','44');
    Que ici cela repère également le 06 même si dans la chaîne il est sous la forme de 0678.

    J'espère avoir été clair ^^ c'est dur à expliquer
    Merci d'avance !

  2. #2
    Inactif  
    Homme Profil pro
    Inscrit en
    Janvier 2014
    Messages
    374
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations forums :
    Inscription : Janvier 2014
    Messages : 374
    Points : 479
    Points
    479
    Par défaut
    Bonjour !

    Cela sert à quoi ?... Ce sont des départements ?...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $keyWords = array('06', '96', '12', '44');
    Je pense que si dans la variable '$sentence' les données ne sont pas bien structurées, vous n'allez pas vous en sortir!

    Ou alors, dans un 1er temps il faut la passer dans une "moulinette", qui va la remettre d'équerre. Non ?...

  3. #3
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2012
    Messages
    63
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2012
    Messages : 63
    Points : 33
    Points
    33
    Par défaut
    Non, non ce ne sont pas des départements xD
    Mais mon code marche très bien, c'est juste que deux boucles pour faire ça me semble un peu suspect. C'était pour savoir si quelqu'un n'avait pas une solution à base de preg_match_all ou un truc du genre, qui tient en 2 lignes ^^

  4. #4
    Inactif  
    Homme Profil pro
    Inscrit en
    Janvier 2014
    Messages
    374
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations forums :
    Inscription : Janvier 2014
    Messages : 374
    Points : 479
    Points
    479
    Par défaut
    Non non !
    - Il faut 2 boucles pour comparer chaque élément de '$sentence' avec ceux du '$keyWords'.
    - C'est d'ailleurs pour cela que '$sentence' doit être propre et pas bancale !

  5. #5
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2012
    Messages
    63
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2012
    Messages : 63
    Points : 33
    Points
    33
    Par défaut
    Justement ! Ma question revient au même que "Est-ce qu'il est vraiment nécessaire de comparer chaque élément de la phrase avec chaque élément du tableau ?". Et je ne pense pas... D'où ma raison de croire qu'il y a une possibilité d'amélioration (d'enlever une boucle).

    Regarde par exemple, ceci, ça ne ferait pas l'affaire ?

    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
    function fonc($sentence, $tableOfKeyWords) {
        $counter = 0;
        // Pour chaque mot clef.
        foreach ($tableOfKeyWords as $value2) {
            // Si ces deux mots sont égaux.
            if (preg_match('/\b' . $value2 . '/i', $sentence)) {
                // On incrémente le compteur.
                $counter++;
     
                if ($counter >= 3) {
                    return true;
                }
            }
        }
        return false;
    }

  6. #6
    Inactif  
    Homme Profil pro
    Inscrit en
    Janvier 2014
    Messages
    374
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations forums :
    Inscription : Janvier 2014
    Messages : 374
    Points : 479
    Points
    479
    Par défaut
    Oui !
    - Vous pouvez remplacer la boucle interne par une regex !
    - il demeure néanmoins que '$sentence' doit être propre et pas bancale !

  7. #7
    Expert éminent Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 858
    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 858
    Points : 6 556
    Points
    6 556
    Par défaut
    Effectivement tu peux améliorer ton code en utilisant preg_match_all qui a l'avantage de renvoyer le nombre d'occurrences trouvées, de ne parcourir qu'une seule fois la chaîne et de pouvoir tester tous les mots-clefs d'un coup. Exemple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    function fonc($sentence, $keywords) {
        $pattern = '~(?<=[\' ]|\A)(?:' . implode('|', $keywords) . ')(?=[\' ]|\z)~';
        return preg_match_all($pattern, $sentence)>2;
    }
    L'idée est de construire le pattern en agglutinant les mots-clefs avec | (le OU logique) grâce à implode(), ce qui donne: mot1|mot2|mot3....
    On met le tout dans un groupe non-capturant (?:mot1|mot2|mot3...) pour pouvoir mettre en facteur les limites à gauche (?<=[' ]|\A) et à droite (?=[' ]|\z).

    (?<=...) est un test arrière (lookbehind) et (?=...) un test avant (lookahead). Ils ont l'avantage de vérifier une condition sans pour autant faire partie de la correspondance.

    Je me suis directement inspiré de ton code en ce qui concerne ces limites qui n'autorise que l'espace, le quote et les limites de chaîne. Peut-être ne sont elles pas appropriées pour ce que tu veux faire. Dans ce cas, libre à toi d'en changer (voire même d'ajouter deux paramètres à la fonction, afin de les adapter à chaque type de liste de mots-clefs).
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    function fonc($sentence, $keywords, $left='(?<=[\' ]|\A)', $right='(?=[\' ]|\z)') {
        $pattern = '~' . $left . '(?:' . implode('|', $keywords) . ')' . $right . '~';
        return preg_match_all($pattern, $sentence)>2;
    }
     
    fonc('33t ABC_8 78t def7 45t', array('33t', '45t', '78t'), '\b', '\b');
    Attention tout de même à la solution un peu naïve qui consiste à utiliser \b pour délimiter des mots ou des chiffres. \b est la limite entre un caractère appartenant à la classe de caractère \w et autre chose (i.e. un caractère n'y appartenant pas ou une limite de chaîne). Par défaut en PHP, \w est censé représenter [A-Za-z0-9_] (donc \b ne correspond pas à la limite entre une lettre et un chiffre, un chiffre et un souligné, etc.), mais ce n'est pas toujours le cas. Si le module PCRE (le module de regex de PHP) a été compilé en tenant compte des locales, \w comprendra également les caractères accentués des dites locales, ou, bien que ce ne soit pas le cas ici, si le modificateur u est utilisé, \w pourrait alors comprendre toute lettre ou chiffre de la table unicode.
    Brachygobius xanthozonus
    Ctenobrycon Gymnocorymbus

  8. #8
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2012
    Messages
    63
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2012
    Messages : 63
    Points : 33
    Points
    33
    Par défaut
    Merci CosmoKnacki, c'est de ce genre d'optimisation dont je parlais !
    03h01 ! T'es pas une marmotte comme les autres ^^

    Sinon je ne sais pas ce que font les \A et \z dans les délimiteurs droite et gauche... Quelqu'un pourrait t'il me le dire ?

    Puis en réalité j'aimerais que la fonction accepte l'ajout de suffixe mais pas celui de préfixe. C'est pour cela que j'avais mis mon \b seulement devant dans mon exemple. En gros dans ta fonction ce serait tout simplement $left = '\b'et $right = ''

    EDIT : Puis il faudrait que ça prenne les différentes casse, majuscule et minuscule. Mettre un \i qqpart ^^

  9. #9
    Expert éminent Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 858
    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 858
    Points : 6 556
    Points
    6 556
    Par défaut
    \A et \z sont des ancres qui marquent le début et la fin de la chaîne. Elles ont l'avantage, comparé à ^ et $, de ne pas changer de sens quand on utilise le mode multiline (modificateur m), mode dans lequel ^ et $ marquent alors le début et la fin de la ligne. De plus en mode "normal" $ peut marquer la fin de chaîne mais aussi la position avant le dernier caractère si le dernier caractère est une nouvelle ligne \n. Utiliser \A et \z lève donc toute ambiguïté.

    Pour ce qui est du suffixe et du préfixe, je pense que tu pourras t'en débrouiller. En ce qui concerne, le non-respect de la casse, il faut effectivement utiliser le modificateur i soit en le plaçant après le délimiteur de fin qui dans mon exemple est un tilda ~, ou plus élégant, en le mettant ici: (?i:mot1|mot2....)
    Brachygobius xanthozonus
    Ctenobrycon Gymnocorymbus

  10. #10
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2012
    Messages
    63
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2012
    Messages : 63
    Points : 33
    Points
    33
    Par défaut
    Ah merci, je savais pas du tout pour les \A et \z, d'ailleurs je n'ai jamais utilisé ~, j'utilisais / et donc je finissais toujours par /i pour outrepasser la casse. Mais merci de m'aider à m'améliorer xD
    Et le problème avec le \b c'est que ton \A me gène un peu, je ne sais plus où le mettre MDR

  11. #11
    Expert éminent Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 858
    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 858
    Points : 6 556
    Points
    6 556
    Par défaut
    Pour clarifier, ce que j'ai mis en lieu et place de ton \b, qui signifie dans ce contexte "précédé par un caractère n'appartenant pas à \w ou par le début de la chaîne", c'est (?<=[\' ]|\A) qui signifie "précédé par un simple quote ou un espace ou le début de la chaîne", donc si tu préfères utiliser \b, remplace le tout, tu n'as pas besoin de recaser \A ou quoi que ce soit d'autre.

    En fait le "word boundary" \b, si tu le places au début est équivalent à (?<=\W|\A)(?=\w) voire même (?<=\W|\A) tout court puisque les mots sont alors censés être constitués de caractères appartenant à \w.

    Quand j'écris (?<=[' ]|\A), ce n'est ni plus ni moins qu'une sorte de "word boundary" customisé (dans le sens où l'on sait que dans ce contexte les mots sont toujours séparés par des espaces ou des simples quotes, ce que laissait entendre ton code de départ).

    Pour trouver le "word boundary" adapter à la situation, tu dois te demander quel est l'ensemble (exhaustif) de caractères qui peuvent constituer les mots, et quels sont les caractères qui peuvent les séparer.
    Brachygobius xanthozonus
    Ctenobrycon Gymnocorymbus

  12. #12
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2012
    Messages
    63
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2012
    Messages : 63
    Points : 33
    Points
    33
    Par défaut
    Je viens de m'apercevoir que mon message de samedi matin n'avait pas était posté, ma connexion sans doute La cata xD

    Je voulais dire que j'en était arrivé à ça, sous tes conseils :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    function fonc1($sentence, $keywords, $threshold, $left = '(?<=[\' ]|\b)', $right = '(?=[\' ]|\z)') {
        $matches = array();
        $pattern = '~' . $left . '(?i:' . implode('|', $keywords) . ')' . $right . '~';
        return preg_match_all($pattern, $sentence, $matches) >= $threshold;
    }
    Mais que mon problème est que ça ne donne pas la même chose que lorsque je compare les résultats sur mon jeu de données avec le résultat de ma première fonction tout en haut. Mais je n'ai pas la moindre idée du pourquoi du comment...

  13. #13
    Expert éminent Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 858
    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 858
    Points : 6 556
    Points
    6 556
    Par défaut
    Peux-tu poster un extrait de ton jeu de données. En fait j'ai l'impression que le problème est juste dans le fait de déterminer précisément ce qui sépare tes données les unes des autres (ce qui permettra de déterminer les paramètres $left et $right appropriés.

    Autre chose qui pourrait être source d'erreur: si ton tableau de mots-clefs contient des éléments avec des caractères spéciaux pouvant être interprétés par le moteur de regex, cela peut s'avérer problématique. Pour éviter ce cas de figure, tu peux passer tous les éléments du tableau $keywords par la fonction preg_quote qui échappe ces caractères en question (pour qu'ils soient vus comme des litéraux.)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    function fonc1($sentence, $keywords, $threshold, $left = '(?<=[\' ]|\b)', $right = '(?=[\' ]|\z)') {
        $keywords = array_map(function ($item) { return preg_quote($item, '~'); }, $keywords);
        $pattern = '~' . $left . '(?i:' . implode('|', $keywords) . ')' . $right . '~';
        return preg_match_all($pattern, $sentence) >= $threshold;
    }
    note: il n'est pas nécessaire d'utiliser le 3e paramètre de preg_match_all.
    Brachygobius xanthozonus
    Ctenobrycon Gymnocorymbus

  14. #14
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2012
    Messages
    63
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2012
    Messages : 63
    Points : 33
    Points
    33
    Par défaut
    Lorsque je met pas le 3èm paramètre, il me sort un avertissement, peut-être que mon interpréteur est en niveau trop stricte :O
    Pour le preg_quote, ouais, je l'avais mis dès le début tkt, je me suis tellement fais avoir par le passé.

    Quant à mon jeu de données, il s'agit de mots et de chiffres représentant des identifiants de compte. Je ne peux pas et dois pas mieux les formater ou autre. Il peuvent être séparé par tout ce qui est susceptible de séparer des mots. Donc des blancs, espaces, retours chariot... et des ' bien sûr.

  15. #15
    Expert éminent Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 858
    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 858
    Points : 6 556
    Points
    6 556
    Par défaut
    Tiens, c'est bizarre ça, le 3e paramètre est pourtant optionnel, peut-être qu'il ne l'était pas dans les versions précédentes de PHP.

    Attention au terme "mot" qui est plutôt flou, ce qui peut être gênant dés lors qu'il s'agit d'être précis. Pour ma part, je l'interprète comme "lettres" (et qui plus est de la table ASCII, merci de le confirmer)
    Si tes mots-clefs sont uniquement constitués de lettres et de chiffre (de la table ASCII j'imagine, donc pas d'accents ni de chiffres en ouzbek du 12e siècle), tu peux utiliser ça comme délimiteurs:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $result = fonc1($sentence, $keywords, 3, '(?<![A-Za-z0-9])', '(?![A-Za-z0-9])');
    (En d'autres termes: non précédé et non suivi d'une lettre ou d'un chiffre de la table ASCII)

    Pour des chiffres ou lettres dans n'importe quel alphabet:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $result = fonc1($sentence, $keywords, 3, '(?<![\pL\pN])', '(?![\pL\pN])');
    Brachygobius xanthozonus
    Ctenobrycon Gymnocorymbus

  16. #16
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2012
    Messages
    63
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2012
    Messages : 63
    Points : 33
    Points
    33
    Par défaut
    Re ! Le mystère du 3èm argument... Pourtant dans la doc, il y a bien marqué facultatif, va savoir ce que j'ai foutu xD

    Que je mette :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $left = '(?<=[\' ]|\b)', $right = '(?=[\' ]|\z)'
    ou
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $left = '(?<![\pL\pN]\b)', $right = '(?![\pL\pN])'
    J'ai bien l'impression que cela me donne la même chose.
    Merci au passage pour les \pL\pN, je ne connaissais pas non plus.

    Enfin bref, merci, tu m'as plus qu'aider, j'ai la réponse à ma question en gros, mes 2 boucles tiennent maintenant en une ligne, avec un gain de temps de 15 à 30% sur mes jeux de tests.
    Je te suis éternellement reconnaissant
    Maintenant à moi de me débrouiller avec mes limites droites et gauches pour opti le truc.

    Encore merci !!!!!!!!!

Discussions similaires

  1. Vérification motifs dans chaine : preg_match_all
    Par 0.GeGe.0 dans le forum Langage
    Réponses: 2
    Dernier message: 30/04/2014, 11h19
  2. [Batch] Problème de vérification d'une chaîne dans une boucle
    Par slyfennec dans le forum Scripts/Batch
    Réponses: 2
    Dernier message: 28/04/2014, 17h06
  3. Remplacer des motifs dans une chaîne
    Par tonguim dans le forum Langage
    Réponses: 1
    Dernier message: 30/05/2009, 17h49
  4. Optimiser les jointures dans des requêtes
    Par klereth dans le forum PostgreSQL
    Réponses: 12
    Dernier message: 23/04/2005, 17h29
  5. [OPTIMISATION] [UNION] Union dans une requete
    Par nico44 dans le forum Requêtes
    Réponses: 2
    Dernier message: 10/03/2005, 12h47

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