Forum Linux.débutant Perl - faire ressortir les définitions d'une liste de mots

Posté par  . Licence CC By‑SA.
3
4
avr.
2022

Bonsoir,
Débutante en perl cette année, nous devons fournir un projet prochainement. J'ai décidé de faire ressortir d'un texte français (si_c'est_un_homme.txt) tous les mots étrangers. Pour se faire, dans un premier script j'ai utilisé des expressions régulières qui testaient l'existence des mots du texte dans un dictionnaire français. Ce premier script m'a permis de sortir une liste ordonnée avec une seule occurence par mot > listemots.txt.

Dans un second script je souhaitais ouvrir cette liste ainsi qu'un second dictionnaire (les-100-mots-de-la-shoah) dans lequel les définitions des termes étrangers apparaissent entre guillemets. Seulement ce second script ne donne rien, pas même un message d'erreurs soulignant une faute à une quelconque ligne, juste rien. Pourriez-vous m'indiquer ce qui semblerait bloquer ?

Voici le code : avec beaucoup de commentaires :

use strict;   # mode algorithmique
use warnings; # messages d'alerte
use utf8;  # caractères accentués dans le code Perl
binmode(STDOUT,':utf8'); # caractères accentués dans le terminal 
#OUVRIR LA LISTE DES MOTS contenu dans listemots.txt 
#enregistrer ces mots dans une variable dans un tableau_

my @liste = ();
open(LST,'listemots.txt'); #ouvrir la liste des mots créée dans le précédent script perl
binmode(LST,':utf8');
while (my $mot = \<LST >) {
  chop $mot;
  push (@liste, $mot); #création d'une liste contenant ces mots dans le second script
}
close(LST); # Si j'écris print ici : les mots de la liste s'affichent bien.

open(LST, 'les-100-mots-de-la-shoah.txt');
binmode(LST,':utf8'); # caractères accentués dans le terminal
while (my $ligne = \<LST>) { #traitement par ligne
chop $ligne;# suppression du retour chariot

foreach my $mot (@liste) {
 if ($ligne =~ /$mot,\s"\s(.+?)\si"/) { 
  print "»$ligne«\n";
  }
 }
}
close (LST);

PROBLEME : rien ne s'affiche (sauf les mots de la liste si j'écris print dans le premier tableau)

  • # regexp

    Posté par  (site web personnel, Mastodon) . Évalué à 3.

    En lisant en diagonale, et la coloration syntaxique semble corroborer cela, ton erreur est dans la comparaison

     if ($ligne =~ /$mot,\s"\s(.+?)\si"/) {

    Il faut que tu préparer séparément ta variable pour comparer deux variables… selon ce qui est indiqué ci et

    Tu peux regarder aussi l'opérateur cmp (qui a ma préférence)

    “It is seldom that liberty of any kind is lost all at once.” ― David Hume

  • # i en trop en fin d'expression rationnelle ?

    Posté par  (site web personnel, Mastodon) . Évalué à 2.

    Cela fait longtemps que je n'ai pas fait de Perl, est-ce qu'il n'y aurait pas un i en trop en fin d'expression rationnelle ? Ici :

    \si"/
    
    • [^] # Re: i en trop en fin d'expression rationnelle ?

      Posté par  . Évalué à 3.

      Le i pour dire insensible à la casse devrait être après l’expression rationnelle, donc après le slash : \s"/i

      Cela dit, sans un exemple de ligne à attraper, il est impossible de voir si l’expression rationnelle est bonne.

      Plus généralement, quand une expression rationnelle n’attrape rien, une solution est de la simplifier, quitte à ce qu’elle attrape trop de trucs, et de la recomplexifier progressivement.

      Par exemple, si replacer i ne suffit pas à ce que ça marche, tu commences par /$mot/i (ça attrapera aussi les lignes ou le mot est dans une définition) et tu ajoutes les autres éléments, un à un.

      Mais si tes lignes commencent par le mot, cette expression suffirait peut-être : /^$mot,/

      À part ça, je ne vois pas à quoi te sert l’antislash devant tes opérateurs diamants : while (my $ligne = \<LST>)
      Je n’ai jamais vu cette syntaxe, et à l’essai, elle semble plutôt empêcher le fonctionnement. Si elle sert à quelque chose, je serais curieux de savoir à quoi.

      « Le fascisme c’est la gangrène, à Santiago comme à Paris. » — Renaud, Hexagone

  • # Ça remonte à loin

    Posté par  (site web personnel) . Évalué à 1. Dernière modification le 09 avril 2022 à 12:32.

    Si j'ai bien compris ce que tu essayes de faire :
    - ton fichier listemots.txt contient la liste des mots qui n'ont pas été trouvés dans ton premier dictionnaire et qui sont donc 'potentiellement' des termes étrangers.
    - Ton fichier les-100-mots-de-la-shoah.txt contient, je cite : "les définitions des termes étrangers apparaissent entre guillemets".

    Ensuite, dans ton script, tu fais un test via une expression régulière et tu souhaites afficher les cas positifs :

     if ($ligne =~ /$mot,\s"\s(.+?)\si"/) { 
      print "»$ligne«\n";
      }

    Essayons de traduire en français ce qu'essaye de faire ton test if :

    Si le scalaire $ligne correspond au motif défini ainsi alors affiche le scalaire $ligne à l'écran.
    (Dans la phrase ci-dessus, le mot "ainsi" désigne ton expression régulière)

    Essayons maintenant de traduire ton expression régulière "ainsi" en français (j'essayerais de donner des exemples pour illustrer), et je vais la construire progressivement (pédagogie, quand tu nous tiens…) :

    Je recherche un texte qui contient le scalaire $mot (je simplifie beaucoup en réalité…) :
    /$mot/

    Test réussi : si $mot = "man" et que $ligne = "man" ou "amant" ou "maman" par exemple
    Testé échoué : si $mot = "man" et que $ligne = "nam" ou "nanam" ou "toto" par exemple

    L'expression régulière /$mot/ n'est donc pas assez rigoureuse pour caractériser précisément le terme que tu cherches, tu a donc complexifier ton expression régulière afin d'être plus précise :

    Je recherche un texte qui contient le scalaire $mot, suivi d'une virgule :
    /$mot,/

    Test réussi : si $mot = "man" et que $ligne = "man," ou "aman,t" ou "maman," par exemple
    Testé échoué : si $mot = "man" et que $ligne = "nam," ou "nanam," ou "toto" par exemple

    L'expression régulière /$mot,/ n'est donc pas assez rigoureuse pour caractériser précisément le terme que tu cherches (elle réponds positif à "maman,"), tu a donc continué à complexifier ton expression régulière :

    Je recherche un texte qui contient le scalaire $mot suivi d'une virgule, suivi d'un métacaractère \s symbolisant un espace (je simplifie…) :
    /$mot,\s/

    Test réussi : si $mot = "man" et que $ligne = "man, maman" ou "aman, t" ou "maman, papa" par exemple
    Testé échoué : si $mot = "man" et que $ligne = "man,maman" ou "nanam, hi" ou "toto" par exemple

    L'expression régulière /$mot,\s/ n'est donc pas assez rigoureuse pour caractériser précisément le terme que tu cherches (elle réponds positif à "maman, papa") et à ce moment-là, je pense que tu as commis une erreur typique des jeunes programmeurs (ne le prends pas mal, on est tous passé par là).

    En gros, tu a perdu de vue ce que tu cherchais vraiment à faire dans ton test if et tu t'es tellement concentré sur ton expression régulière que tu l'as complexifié jusqu'à ce qu'elle représente correctement l'ensemble de la phrase présente dans ton dictionnaire les-100-mots-de-la-shoah.txt. J'explique ça en fin de commentaire.

    Continuons sur ta construction d'expression régulière, un peu plus rapidement :

    Je recherche un texte qui contient le scalaire $mot suivi d'une virgule, suivi d'un espace, suivi d'un caractère double-guillemets :
    /$mot,\s"/

    puis :

    Je recherche un texte qui contient le scalaire $mot suivi d'une virgule, suivi d'un espace, d'un double-guillemets et d'un métacaractère \s symbolisant un espace (je simplifie…) :
    /$mot,\s"\s/

    Test réussi : si $mot = "man" et que $ligne = /man, " maman/ ou /aman, " t/ ou /maman, " papa/ par exemple
    Testé échoué : si $mot = "man" et que $ligne = /man, " maman/ ou /nanam, hi/ ou /man, "désigne un être humain de sexe masculin/ par exemple
    À noter : dans les deux tests précédents, j'ai été obligé de modifier la syntaxe : j'utilisais auparavant les guillemets double pour délimiter les valeurs mais comme ton expression régulière utilise des guillemets doubles, j'ai remplacé mes guillemets-doubles par des slash.

    Continuons sur ta construction d'expression régulière, ensuite tu a rajouté (.+?) :
    /$mot,\s"\s(.+?)/

    Commençons par les parenthèses que tu as rajouté, en Perl 5, mettre des parenthèses dans une expression régulière, ça sert à mémoriser la séquence de caractères contenue entre les parenthèses en vue d'une ré-utilisation plus tard dans ton script. Il s'agit d'une opération coûteuse en terme d'utilisation des ressources de l'ordinateur : tu lui demande d'identifier les caractères correspondants au motif .+? et de le mémoriser dans un scalaire (une variable en gros) pour la réutiliser plus tard. Ça prends du temps de calcul processeur et aussi de la mémoire vive.

    Concentrons-nous maintenant sur cette séquence de caractères que tu cherches non seulement à identifier mais également à mémoriser, à savoir :
    .+?

    Le point est un métacaractère signifiant "je cherche n'importe quel caractère sauf un retour à la ligne". En passant, tu as déjà supprimé le retour chariot juste avant ton if via l'instruction suivante, n'est-ce pas ? :

    chop $ligne;# suppression du retour chariot

    Par conséquent, en mettant un point dans ton expression régulière, tu recherches tout caractère sauf un retour chariot alors que l'instruction chomp te certifie qu'il n'y en a pas un seul. C'est pas grave mais ça me choque un peu quelque part, c'est pas très cohérent quand on y réfléchit.

    À moins bien sûr que tu n'ait fait une erreur quelque part, par exemple en confondant chop et chomp…
    Du coup, ton commentaire "# suppression du retour chariot" n'est-il pas erroné ?
    De même, ton commentaire sur la dernière ligne de code suivante n'induit-il pas son lecteur en erreur vu ce qu'il affirme :

      chop $mot;
      push (@liste, $mot); #création d'une liste contenant ces mots dans le second script
    }
    close(LST); # Si j'écris print ici : les mots de la liste s'affichent bien.

    Moi j'aurais écrit un truc du genre :

    close(LST); # Si j'écris print ici : les mots de la liste s'affichent bien si il y avait un retour chariot à la fin du fichier source, sinon ça affichera probablement les mots de la liste correctement mais il manquera peut-être le dernier caractère de la dernière ligne du ficher si ce fichier ne se termine pas par un retour chariot.

    À la suite de ce point, tu places un quantificateur + dans ton expression régulière :
    .+

    Le quantificateur + sert à dire que tu recherches une ou plusieurs occurrence(s) du caractère situé juste avant le +.
    Ainsi, .+ signifie en français : une suite la plus longue possible de caractères qui ne sont pas des retours à la ligne.
    Je précise bien qu'il s'agit de la suite la plus longue de caractères qui ne sont pas des retours à la ligne par ce que le quantificateur + est avide, c'est à dire qu'il correspondra la la liste de caractères la plus longue possible.

    Normalement, certains lecteurs doivent déjà avoir levé un sourcil à ce stade de la lecture.
    En effet :
    - On sait que $ligne ne contient pas de retour chariot (enfin si chomp était correctement utilisé à la place de chop).
    - On recherche à obtenir le maximum de caractères qui ne sont pas des retours chariot.

    La conséquence logique de ces deux faits, c'est qu'entre ces deux parenthèses se retrouvera tout ce qui reste de la chaîne de caractères : il ne restera RIEN après, rien du tout.

    Les programmateurs perl expérimentés m'excuseront de simplifier tout un tas de choses, ce que j'écris n'est valable que si l'output_record_separator() n'a pas été modifié, tout ça, tout ça,…)

    Je résume donc l'expression régulière à ce stade :
    Je recherche un texte qui contient le scalaire $mot suivi d'une virgule, suivi d'un espace, d'un double-guillemets et d'un espace. Ensuite, je cherche et je veux mémoriser la suite la plus longue possible de caractères qui ne sont pas des retours à la ligne.

    Reprenons sur ta construction d'expression régulière, voici la suite :
    /$mot,\s"\s(.+?)\si"/

    En français, ça donne :

    Je recherche un texte qui contient le scalaire $mot suivi d'une virgule, suivi d'un espace, d'un double-guillemets et d'un espace. Ensuite, je cherche et je veux mémoriser la suite la plus longue possible de caractères qui ne sont pas des retours à la ligne. Une fois cette suite la plus longue trouvée, il faut également qu'elle soit suivie d'un espace, du caractère i et d'un double-guillemet.

    Une piste de test (niveau nul) pour l'étudiante :
    - Lis les commentaires précédents (tes slashs devant les opérateurs diamant…)
    - changes tes chop par des chomp
    - essaye d'utiliser une expression régulière de ce type : /$mot,\s"\s(.+?)/
    - Mets "man" dans ton fichier de mots
    - Mets man, " Désigne un être humain" dans ton dictionnaire des définition de mots étrangers, suivi d'un retour chariot
    - teste

    Il se peut que je me trompes complètement, ça fait des années que je ne fais plus de regex et j'ai pas d'interprétateur PERL sous la main pour tester.

    Une meilleure piste pour l'étudiante, j'entre dans le domaine des suppositions et je me trompe peut-être en disant plus haut :
    En gros, tu a perdu de vue ce que tu cherchais vraiment à faire dans ton test if et tu t'es tellement concentré sur ton expression régulière que tu l'as complexifié jusqu'à ce qu'elle représente correctement l'ensemble de la phrase présente dans ton dictionnaire les-100-mots-de-la-shoah.txt.

    D'après ce que je comprends des intentions de programmation de l'étudiante, son fichier dictionnaire doit ressembler à ça :

    woman, "Désigne un être humain de sexe masculin"
    man, "Désigne un être humain de sexe féminin"
    mussel, "Désigne un individu glandant sur Linuxfr"

    Si c'est bien le cas, ce que tu devrais chercher en fait, c'est l'expression régulière suivante :
    En français : je recherche un texte qui commence par le scalaire $mot
    En regex PERL5 : /^ $mot/
    (mais sans l'espace entre le ^ et $mot, je n'arrive pas à écrire ça sur Linuxfr, sans l'espace ça donne ça : /$mot/).
    Cette solution est beaucoup plus simple que ta regex qui cherchait à décrire l'ensemble de la phrase du fichier. Quand tu y réfléchis, la phrase complète, tu t'en fous, ce qui compte c'est que la phrase commence bien par le terme que tu recherches.

    Si l'étudiante souhaite avoir une meilleure note, il faudrait qu'elle envisage d'utiliser des hachages %liste à la place d'un tableau @liste. En passant, on parle de listes en PERL, jamais de tableaux. En effet, l'étudiante affirme "Ce premier script m'a permis de sortir une liste ordonnée avec une seule occurrence par mot". Il s'agit d'un avantage énorme, on sait que la liste est ordonnée (on s'en fout un peu dans le cas présent mais c'est un bel effort de l'avoir signalé) mais surtout qu'il n'y a qu'une seule occurrence par mot. On peut donc utiliser un hachage avantageusement pour la suite des calculs.

    Pour terminer, si l'étudiante souhaite obtenir un score maximal, je lui conseille de rédiger son script en PERL version 6, normalement le prof' devrait être sensible à ce genre d'initiative et si ce n'est pas le cas, l'étudiante pourra toujours argumenter en disant que le prof' n'a pas précisé explicitement la version de PERL à utiliser.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.