Forum |  HardWare.fr | News | Articles | PC | S'identifier | S'inscrire | Shop Recherche
1337 connectés 

  FORUM HardWare.fr
  Programmation
  Perl

  [Résolu] Gestion d'un Top10 en %hash et de la "casse"

 


 Mot :   Pseudo :  
 
Bas de page
Auteur Sujet :

[Résolu] Gestion d'un Top10 en %hash et de la "casse"

n°2105820
Sethenssen
Posté le 11-10-2011 à 19:48:45  profilanswer
 

Bonsoir,
 
Je n'arrive pas à réaliser la même chose sous Perl que mon résultat en Awk.
J'ai essayé le awk2perl mais sans succès.
 
Mon souhait est assez simple, j'ai un fichier de sortie que je split, puis sur 2 colonnes je veux savoir le nombre d’occurrence totale et les trier.
Avec un exemple cela sera plus parlant.
 
Mon fichier de donnée:

Code :
  1. 4F8939933;TT;355DLFO5;PAS 01;PAS 01;599;20111010000051;;FDM;599;66;3;20111010000051;
  2. 4F8964454;TT;8458DKII2;THD 4;Numero 1;399;20111010000050;;FDM;399;66;3;20111010000050;
  3. 4F84RF200;TT;544DKLK25;THD 4;Numero 1;0;20111010000040;;FDM;0;66;3;20111010000040;
  4. 4F8FR57001;TT;6017EDDL;VPL 5;Table 5;0;20111010000036;;FDM;0;66;3;20111010000036;
  5. 4F8304990;TT;8043DFL3;VRP 02;Chaise 3;0;20111010000020;;FDM;0;66;3;20111010000021;
  6. 565657244;TT;383EEDD22;THD 4;Numero 1;0;20111010000021;;FDM;0;66;3;20111010000021;
  7. 4F8318223;TT;70EEDC0274;PAS 01;Souris;0;20111010000019;;FDM;0;66;460;20111010000019;
  8. 5FE2E2E036;TT;8AAZ590;VRP 02;Chaise 3;0;20111010000018;;FDM;0;66;3;20111010000018;
  9. 4F83FS201;TT;82QSX584;PAS 01;Souris;0;20111010000009;;FDM;0;66;3;20111010000009;
  10. 5658RZER758;TT;82DCD91;PAS 01;Souris;0;20111010000005;;FDM;0;66;3;20111010000005;
  11. 4F83FZES201;TT;82QSXZE584;PAS 01;Souris;0;20111010010009;;FDM;0;66;3;20111010010009;


 
Ma commande en awk:

Code :
  1. cat test | awk -v FS=";" '{print substr($4,1,3)";"$5}' | sort -f | uniq -ic | sort -f -n -k1,1 -r | awk 'NR == 1 , NR == 5' | awk '{if (NR<10) {print 0NR":"$0} else {print NR":"$0}}'


 
Mon résultat:

Code :
  1. 01:      4 PAS;Souris
  2. 02:      3 THD;Numero 1
  3. 03:      2 VRP;Chaise 3
  4. 04:      1 VPL;Table 5
  5. 05:      1 PAS;PAS 01


 
C'est exactement ce que je souhaite,
J'ai ici un genre de TOP5.
Avec le Awk j'affiche même les n° de ligne.
 
Mon script Perl ci-dessous n'apporte évidement pas la solution,
J'ai beau essayé avec des sorts array, des map, je n'arrive pas au même résultat.
C'est un aspect que je ne maîtrise pas encore et les recherches sur le net restent pour le moment infructueux.
Là où je pèche vraiment c'est sur l'équivalent du "uniq -c"
 

Code :
  1. #!/usr/bin/perl -w
  2. use warnings;
  3. use strict;
  4.  
  5. use MIME::Lite;
  6. use Net::Ping;
  7. use File::Copy;
  8. use File::Find;
  9. use Encode;
  10. my ($i, $y) = (0, 0);
  11. opendir(DIR, $DIR) or die "Can't open $DIR: $!";
  12.     my $SEARCH_FILE = "billing_$mday-$mon-$year";
  13.     my @bil = grep(/$SEARCH_FILE/,readdir(DIR));
  14.     closedir(DIR) or warn "Can't close $DIR: $!";
  15.     if (@bil > 0)    {
  16.         foreach my $file (@bil)    {
  17.             open (FILE, "<", $DIR . $file);
  18.             while (<FILE> )    {
  19.                 chomp $_;
  20.                 my @tab=split(/;/,$_);
  21.                 if ($tab[11] == 490)    {
  22.                     next;
  23.                 }
  24.                 if (! $hash{$tab[0]."|".$tab[2]."|".$tab[3]."|".$tab[6]})    {
  25.                     $hash{$tab[0]."|".$tab[2]."|".$tab[3]."|".$tab[6]}=1;
  26.                     $nbligne=keys(%hash);
  27.                     if ($tab[5] == 0)    {
  28.                         ++$i;
  29.                         print $tab[3]."|".$tab[4]. "\n";
  30.                     }
  31.                     else {
  32.                         ++$y;
  33.                     }
  34.                 }
  35.             }
  36.             close (FILE) or warn "Message : $!\n";
  37.         }
  38.         foreach my $del (@bil)    {
  39.             unlink $DIR.$del;
  40.         }
  41.     }
  42. }


 
 
Merci pour votre aide.
Cordialement.


Message édité par Sethenssen le 13-10-2011 à 16:44:28
mood
Publicité
Posté le 11-10-2011 à 19:48:45  profilanswer
 

n°2105822
gilou
Modérateur
Modzilla
Posté le 11-10-2011 à 20:57:04  profilanswer
 

Bon j'ai codé ça vite fait, ça pourrait peut être être avec des noms de variable mieux choisis

Code :
  1. #!/usr/bin/perl
  2.  
  3. # error-check flags
  4. use strict;
  5. use warnings;
  6. # Use readable built-in names
  7. use English qw( -no_match_vars );
  8.  
  9. my $filename = 'data.txt'; # fichier des données
  10. open(my $fh, '<', $filename) or die "$filename: fichier pas trouve!"; # ouverture en lecture
  11. my %stuff;  # nom a remplacer par un plus adapte
  12. while (<$fh> ) {
  13. next if (/^\s*$/); # on saute les lignes vide a tout hasard
  14. my @fields = split /;/, $_;
  15. $stuff{substr($fields[3], 0, 3).';'.$fields[4]}++;
  16. }
  17. close $fh;
  18.  
  19. my $cnt = 0;
  20. foreach my $key (sort {$stuff{$b} <=> $stuff{$a}} (keys(%stuff))) {
  21.  ++$cnt;
  22.   print "$cnt:\t$stuff{$key} $key\n";
  23. }


C'est pourtant simple:
On splitte une ligne en champs sur les ;
my @fields = split /;/, $_;
On ne garde que ce qu'on veux dans les champs.
Manifestement, c'est les 3 premiers caractères du 4e champ, et le 5e champ, avec un ; entre les deux
substr($fields[3], 0, 3).';'.$fields[4]
Je m'en sert comme clé d'un hash, dont la valeur est incrémenté chaque fois que la clé est trouvée
$stuff{substr($fields[3], 0, 3).';'.$fields[4]}++;
Après, c'est juste le fait d'imprimer le hash trié par valeur décroissante (ça c'est un truc que que retiens jamais, mais qu'un coup de google permet de retrouver).
 
Sur vos données:

C:\Perl>perl sparse.pl
1:      4 PAS;Souris
2:      3 THD;Numero 1
3:      2 VRP;Chaise 3
4:      1 VPL;Table 5
5:      1 PAS;PAS 01


A+,


Message édité par gilou le 11-10-2011 à 20:58:32

---------------
There's more than what can be linked! --    Iyashikei Anime Forever!    --  AngularJS c'est un framework d'engulé!  --
n°2105868
Sethenssen
Posté le 12-10-2011 à 00:23:14  profilanswer
 

Il est fort ce Gilou !  :jap:  
Nickel, heureusement que tu es là !
C'est toujours quand c'est simple que j'ai besoin d'un p'tit coup de pouce pour débloquer tout ça.
 
 
Pour la variable $cnt, il me reste juste à afficher un 0 quand c'est inférieur à 10.
Ex: 01, 02, 03
Au lieu de: 1, 2, 3
 
Le code ci-dessous fonctionne comme il faut.
Avez-vous un remarque dessus? Est-ce que la démarche est bonne?
 

Code :
  1. my $cnt = 0;
  2. foreach my $key (sort {$top20{$b} <=> $top20{$a}} (keys(%top20))) {
  3.  if ($cnt < 10) {
  4.      $cnt = sprintf '%.2d', $cnt;
  5.  }
  6.  ++$cnt;
  7.  print "$cnt:\t$top20{$key} $key\n";
  8.  if ($cnt == 20) {
  9.      last;
  10.  }
  11. }


 
Encore merci Gilou.
Cdt.

n°2105873
gilou
Modérateur
Modzilla
Posté le 12-10-2011 à 01:31:14  profilanswer
 

Citation :

Avez-vous un remarque dessus?

Oui, pourquoi faire simple quand on peut faire compliqué?
Vous avez besoin de formatage précis, alors il suffit d'utiliser printf au lieu de print.
Remplacez le
print "$cnt:\t$stuff{$key} $key\n";
par
printf "%.2d:\t%s %s\n", $cnt, $stuff{$key}, $key;
ou bien par  
printf("%.2d:\t%s %s\n", $cnt, $stuff{$key}, $key);  
si vous préférez, avec les parenthèses, que ça soit comme en C.
et c'est réglé.
 
Et sinon, je vois que vous voulez afficher au plus 20 lignes.
Alors ceci convient:

Code :
  1. my $cnt = 0;
  2. foreach my $key ((sort {$stuff{$b} <=> $stuff{$a}} (keys(%stuff)))[0..maxlines(\%stuff, 20)]) {
  3.  printf "%.2d:\t%s %s\n", ++$cnt, $stuff{$key}, $key;
  4. }
  5.  
  6. sub maxlines {
  7.  my ($h, $i, $j);
  8.  ($h, $i) = @_;
  9.  $j = 0+(keys(%$h));
  10.  return (--$i>--$j?$j:$i);
  11. }


Plutot que tester ou on en est dans le corps de boucle, autant ne faire la boucle que sur les 20 premières valeurs triées (si @a est un tableau, @a[0..19] est le tableau des 20 premières valeurs de @a).
Comme on ne sait pas s'il y a 20 valeurs dans le tableau, maxlines vérifie cela, et si le nb de lignes qu'on veut afficher est trop grand, il renvoie le nombre de lignes max du tableau (décrémenté de 1, pour coller avec le fait qu'on commence a compter à partir de 0).
 
A+,


Message édité par gilou le 12-10-2011 à 02:10:01

---------------
There's more than what can be linked! --    Iyashikei Anime Forever!    --  AngularJS c'est un framework d'engulé!  --
n°2106037
Sethenssen
Posté le 12-10-2011 à 17:41:54  profilanswer
 

Bonjour,
 
Excellent !
Merci Gilou, tu m'apprends encore de nouvelles choses.
 
J'ai une dernière question sur le sujet,
Imaginons que pour filtrer encore plus mon top5, je décide de rajouter un champ dans mon hash

Code :
  1. $stuff{substr($fields[3], 0, 3).';'.$fields[4].';'.$fields[3]}++;


 
A ce moment là en faisant un nouveau :

Code :
  1. foreach my $key ((sort {$stuff{$b} <=> $stuff{$a}} (keys(%stuff)))[0..maxlines(\%stuff, 20)]) {
  2.  printf "%.2d:\t%s %s\n", ++$cnt, $stuff{$key}, $key;
  3. }


 
Il va m'afficher toutes les clés de mon hash, dont ce nouveau champ.
Or, je ne veux pas afficher ce nouveau $fields[3] dans mon print.
 
Comment faire?


Message édité par Sethenssen le 12-10-2011 à 17:42:25
n°2106042
gilou
Modérateur
Modzilla
Posté le 12-10-2011 à 18:14:19  profilanswer
 

Le plus simple, c'est de copier $key de que tu veux de $key dans une variable, et utiliser cette variable pour le printf.
Ici, on peut faire plus simple: on peut récupérer directement ce que l'on veut afficher avec une expression régulière sur key:

Code :
  1. my $cnt = 0;
  2. foreach my $key ((sort {$stuff{$b} <=> $stuff{$a}} (keys(%stuff)))[0..maxlines(\%stuff, 20)]) {
  3.  $key =~ m/^(.*);/o;
  4.  printf("%.2d:\t%s %s\n", ++$cnt, $stuff{$key}, $1);
  5. }


 
Bon ensuite, si on veut vraiment un truc compact (donc dans l'esprit perl, mais moins lisible), on peut condenser ça en:

Code :
  1. my $cnt = 0;
  2. foreach ((sort {$stuff{$b} <=> $stuff{$a}} (keys(%stuff)))[0..maxlines(\%stuff, 20)]) {
  3.  printf("%.2d:\t%s %s\n", ++$cnt, $stuff{$_}, (m/^(.*);/o and $1));
  4. }


 
A+,


Message édité par gilou le 12-10-2011 à 18:18:22

---------------
There's more than what can be linked! --    Iyashikei Anime Forever!    --  AngularJS c'est un framework d'engulé!  --
n°2106045
Sethenssen
Posté le 12-10-2011 à 19:09:32  profilanswer
 

Bonjour Gilou,
 
Merci cela fonctionne comme toujours !  
 
Peux-tu me décrypter ton expression régulière?
$key =~ m/^(.*);/o;
De ce que je comprends tu récupères avec le (.*) toutes les clés.
A quoi correspond le "o" et le $1 ?
 
Pour une meilleur lisibilité,
Si je veux changer le séparateur ";" par un "\t"
Je n'arrive pas le changer dans ta regex avec le "\\t"
 
Merci.


Message édité par Sethenssen le 12-10-2011 à 19:23:57
n°2106048
gilou
Modérateur
Modzilla
Posté le 12-10-2011 à 19:52:18  profilanswer
 

$key =~ m/^(.*);/o;  
C'est un truc paumant à première vue, car la syntaxe est inattendue.
ça matche l'expression régulière sur $key ( le =~ ne met rien dans $key ). Le premier et unique groupe matché est mis dans $1, comme d'hab.
Ce groupe est (.*) donc tous les caractères, /^ depuis le début, ;/ et avant un ;
Comme en perl par défaut, le match est maximal, ce sera donc tous les caractères depuis le début et avant le dernier ; de la ligne, donc avant le champ ajouté a la fin et séparé par le ;
le o signifie qu'on ne calcule l'expression régulière qu'une fois, car elle ne change pas entre deux tests (clair, car elle ne contient rien de variable).
 

Citation :

Si je veux changer le séparateur ";" par un "\t"

[:jim's]  
Changer ça ou donc? et pourquoi?
 
A+,


---------------
There's more than what can be linked! --    Iyashikei Anime Forever!    --  AngularJS c'est un framework d'engulé!  --
n°2106053
Sethenssen
Posté le 12-10-2011 à 20:24:51  profilanswer
 

Même dans les explications, ce gilou il est fort.
Merci c'est nettement plus clair !
 
Pour ma dernière demande, je me suis trompé
C'était tout bête
Je faisais:

Code :
  1. $key =~ m/^(.*)\\t/o;


 
Au lieu de:

Code :
  1. $key =~ m/^(.*)\t/o;


 
 
C'était dans le cas ou mon hash aurait eu cette forme:

Code :
  1. $stuff{substr($fields[3], 0, 3)."\t".$fields[4]."\t".$fields[3]}++;


Message édité par Sethenssen le 12-10-2011 à 20:47:13
n°2106070
gilou
Modérateur
Modzilla
Posté le 12-10-2011 à 21:53:50  profilanswer
 

Ah OK!
Comme il y avait déjà des ; dans les lignes, je craignais que ce ne fusse de ceux la qu'il s'agisse.
 
Le truc à savoir en perl, mais qui est très mal documenté, c'est qu'on peut assigner la capture d'un groupe matché à une variable (si on veut ses variables a soi, car les $1, $2... sont réassignées au prochain matching), mais qu'il faut être en contexte de liste, car en contexte scalaire, on a pour valeur 1 s'il y a match de l'expression régulière, et 0 sinon.
 

Code :
  1. my $text = "aa;bb;cc";
  2. # en scalar context
  3. my $foo = $text =~ m/^([^;]+);([^;]+)/;
  4. print $foo, "\n";
  5. # le même, mais en list context
  6. ($foo) = $text =~ m/^([^;]+);([^;]+)/;
  7. print $foo, "\n";
  8. # list context avec deux elements dans la liste
  9. my $bar;
  10. ($foo, $bar) = $text =~ m/^([^;]+);([^;]+)/;
  11. print $foo, " ", $bar, "\n";


C:\Perl>perl test.pl
1
aa
aa bb


 
A+,


Message édité par gilou le 12-10-2011 à 21:56:34

---------------
There's more than what can be linked! --    Iyashikei Anime Forever!    --  AngularJS c'est un framework d'engulé!  --
mood
Publicité
Posté le 12-10-2011 à 21:53:50  profilanswer
 

n°2106073
Sethenssen
Posté le 12-10-2011 à 22:01:39  profilanswer
 

Top !  
Merci Gilou, j'ajoute cela dans mes notes sur Perl.
 
Ultime question,
Grâce à toi mon top 10 est en place,
Cependant, j'aimerai pouvoir gérer la "casse"
 
Je peux avoir des titres avec:
- Chaise
- chaise
 
Du coup, dans le hash il compte cela pour 2.
 
Est-ce possible de dire au hash de ne pas faire attention à la casse?
Tel un "uniq -i" ou un "sort -f" en bash?
 
Ou bien ce n'est pas possible et par conséquent il faut passer par une regex de folie pour gérer la casse sur le champ titre?
 
Sachant que mon champ titre peut comporter des espaces, tirets
Ex: Grey's Anatomy - S8 Ep 01


Message édité par Sethenssen le 12-10-2011 à 22:19:46
n°2106104
gilou
Modérateur
Modzilla
Posté le 13-10-2011 à 00:33:43  profilanswer
 

Ben pour gérer la casse, tu peux faire ça au niveau de la clé: tu met les champs en homogénéité de casse avec lc et uc, et éventuellement, tu rajoutes un coup de ucfirst, si tu veux une première lettre en majuscule.
Par exemple, tu met dans ta cle le premier champ tout en majuscule et le second en minuscule, avec le première lettre en majuscule:  
uc(substr($fields[3], 0, 3)).';'.lc($fields[4])  
pour la clé au lieu de  
substr($fields[3], 0, 3).';'.$fields[4]
Tu peux utiliser uc (tout en majuscules), lc (tout en minuscules) et ucfirst (première lettre en majuscule, on touche pas aux autres), selon tes besoins.
A+,


Message édité par gilou le 13-10-2011 à 00:35:03

---------------
There's more than what can be linked! --    Iyashikei Anime Forever!    --  AngularJS c'est un framework d'engulé!  --
n°2106107
Sethenssen
Posté le 13-10-2011 à 02:26:40  profilanswer
 

Top !  
 
Bien vu.
 
J'imagine que si on veut la première lettre de chaque mot en majuscule,
Il faut combiner le ucfirst avec une regex qui manipule cela dès qu'elle détecte un espace, non?
 
Merci encore Gilou,
Ô Capitaine, mon capitaine  :jap:

n°2106147
gilou
Modérateur
Modzilla
Posté le 13-10-2011 à 11:31:57  profilanswer
 

C'est mieux de faire cela lorsque la regexp détecte un mot, non? donc matcher sur \w+
 

Code :
  1. my $text = "toto tata tutu";
  2. print "$text \n";
  3. $text =~ s/\w+/ucfirst($& )/ge;
  4. print "$text \n";

C:\Perl>perl toto.pl
toto tata tutu
Toto Tata Tutu


$& représente ce qui est matché en tout entre les // (on pourrait faire aussi: s/(\w+)/ucfirst($1)/ge; )
le /e c'est pour que le ucfirst soit exécuté dans l'expression de remplacement et non considéré comme du texte.
le /g, c'est pour que ce soit appliqué répétitivement et pas limité au premier mot trouvé
 
A+,


Message édité par gilou le 13-10-2011 à 11:33:42

---------------
There's more than what can be linked! --    Iyashikei Anime Forever!    --  AngularJS c'est un framework d'engulé!  --
n°2106152
Sethenssen
Posté le 13-10-2011 à 11:52:05  profilanswer
 

Merci Gilou !
 
Encore une explication royale
Merci.

n°2106153
Sethenssen
Posté le 13-10-2011 à 12:07:29  profilanswer
 

Avec le \w il me détecte les accents (é, ê, è ...) comme un mot
 

Code :
  1. toto téata étutu
  2. Toto TéAta éTutu


 
J'essaye d'avoir:

Code :
  1. toto téata étutu
  2. Toto Téata Etutu
  3. (ou sinon le E majuscule avec accent)


 
Sachant qu'avant traitement de ce champ titre, j'utilise le "use Encode" pour réencoder la ligne sur du ISO-8859-1
 

Code :
  1. $tab[4]=encode("iso-8859-1", decode("utf8", $tab[4]));


Message édité par Sethenssen le 13-10-2011 à 12:08:37
n°2106165
gilou
Modérateur
Modzilla
Posté le 13-10-2011 à 13:26:46  profilanswer
 

Si ton fichier est en utf8, il faut l'ouvrir en utf8, et tout collera dans les regexp:
 
open(my $fh, '<:utf8', $filename) or die "$filename: fichier pas trouve!"; # ouverture en lecture
 
Et si je remplace les lignes avec THD 4;Numero en entrées par des lignes avec THD 4;éNuméro
ça me donne en sortie pour ces lignes la
02: 3 THD;Énuméro 1
avec un bon passage a la minuscule accentuée en majuscule accentuée
Bon pour le voir, il faut rediriger la sortie dans un fichier, vu que la fenêtre DOS ne sait pas afficher de l'UTF-8
 
Et sinon (pas en unicode au départ), il vaut mieux tout transcoder en unicode avant traitement, si il y a des caractères spéciaux (accents, etc) qui sont à prendre en compte, vu que les propriétés sur les caractères sont bien gérées au niveau unicode.
 
A+,


Message édité par gilou le 13-10-2011 à 13:30:36

---------------
There's more than what can be linked! --    Iyashikei Anime Forever!    --  AngularJS c'est un framework d'engulé!  --
n°2106183
Sethenssen
Posté le 13-10-2011 à 15:04:41  profilanswer
 

Excellent, avec la simple ouverture en utf8 je n'ai même plus besoin de faire le encode,decode.
 
Merci !


Aller à :
Ajouter une réponse
  FORUM HardWare.fr
  Programmation
  Perl

  [Résolu] Gestion d'un Top10 en %hash et de la "casse"

 

Sujets relatifs
[KSH] Soucis avec un tableau[Lucene] Pseudo-équivalent d'un LIKE '%xxx%' ??
balise TABLE : espace créé au dessus du tableau[Aide] trie d'un tableau par date
Gestion de tableau avec tri automatiqueCopier 37x le même tableau sur une feuille Excel
Produire un tableau html dynamiquementrecréer un tableau après modification d'une valeur
Problème avec un tableau 
Plus de sujets relatifs à : [Résolu] Gestion d'un Top10 en %hash et de la "casse"


Copyright © 1997-2022 Hardware.fr SARL (Signaler un contenu illicite / Données personnelles) / Groupe LDLC / Shop HFR