Compter le nombre de doublons d'un tableau - Perl - Programmation
Marsh Posté le 28-10-2011 à 19:02:29
Si j'ai bien compris, on veut amalgamer les cas du serveur 1 et du serveur 2 pour un même user, le même jour.
Le plus simple:
Tu définis un hash, pour lequel la clé va être l'info discriminante, et la valeur pour une clé, le nombre d'occurences dans le tableau.
Code :
|
C:Perl>perl benji.pl |
Donc le principe,
1) on découpe l'array en champs, avec split (ou une expresssion régulière ad-hoc, selon le format de ses données)
2) On fabrique une clé primaire par la concaténation des champs discriminants, ici, le premier champ $parsed[0] la date, et le second $parsed[1] le nom
3 on crée un hash dont la clé est cette clé primaire, et dont la valeur est incrémenté à chaque fois qu'elle est trouvée dans les données $myhash{$parsed[0].".".$parsed[1]}++;
On aurait tout aussi bien pu faire:
Code :
|
Le reste, c'est l'application de la méthode standard
foreach (sort {$myhash{$b} <=> $myhash{$a}} (keys(%myhash))) {
print $myhash{$_}, " ", $_, "\n";
}
qui permet d'imprimer un hash trié par valeur (le tri est numérique ici, mais il pourrait être lexicographique ou autre)
A+,
Marsh Posté le 29-10-2011 à 15:32:27
Bon, perso, si j'avais eu ceci à faire, j'aurais procédé ainsi:
Code :
|
C:\Perl>perl benji.pl benji.txt |
c'est la même méthode, mais en plus sophistiqué:
Je créée toujours une clé discriminante pour un hash
my $key = (split(/([^,]+,[^,]+)/, $_))[1];
je découpe par morceau contenant une virgule et encadré par deux virgules ou le début/fin de ligne, et je ne prends que le premier morceau,
ça me donne pour une ligne "14/04/2011,titi,serveur1" la portion du début "14/04/2011,titi" qui est ce sur quoi on discrimine les lignes (date et nom)
Mais au lieu de donner au hash comme valeur le nb de fois que la clé à été rencontrée, je donne comme valeur au hash un tableau (anonyme)
$myhash{$key} = [ .. ]
comme premier élément du tableau, je vais mettre le nb de fois que la clé à été rencontrée
ensuite je met le champ nom et le champ date, obtenus a coups de split sur la bonne regexp
(split(/,/, $_))[1,0]: on splitte la ligne sur la virgule, et on ne prend que les champs 1 et 0 donc ce qui est après la 1ère virgule et ce qui est avant, ie le nom et la date
enfin, pour pouvoir éventuellement trier sur la date, j'ajoute un élément composé de l'année suivi du mois suivi du jour, à coup de regexp:
join("", (split(/[\/,]/, $_))[2,1,0])
donc au final, pour une ligne comme
"14/04/2011,titi,serveur1"
j'ai une clé "14/04/2011, titi", et si le hash ne contient pas encore cette clé, je lui ajoute cette clé avec comme valeur le tableau
[1, titi, 14/04/2011, 20110414]
et si le hash contient déjà la clé, j'incrémente juste la première entrée du tableau
[En fait, dans le premier élément, je met 0 a la création, et j'incrémente systématiquement ensuite, c'est équivalent plus simple à relire.]
Ensuite, c'est le code d'impression comme précédemment mais un peu amélioré:
On trie par rang (tri numérique), à rang égal, on trie sur le nom (tri lexicographique) et a rang et nom égal, on trie sur la date (tri numérique sur le dernier champ, qui a été construit pour cela)
Ensuite on imprime le clé du hash et le tableau de ses valeurs sauf la dernière, qui n'était la que pour servir au tri.
L'intérêt ici c'est qu'en changeant l'ordre des tris, je peux exploiter autrement mes données.
Bon après, on peut utiliser les routines de format de perl, pour un rapport plus lisible:
Code : |
C:Perl>perl benji.pl benji.txt |
A+,
Marsh Posté le 03-11-2011 à 14:06:46
Merci pour ta réponse, gilou. Excuse-moi de ne pas t'avoir répondu, je n'ai pas été prévenu des nouveaux messages dans le topic (je croyais que la notification par mail était activée par défaut) :\
Bref, je venais pour clore le sujet. Celui-ci a été résolu sur le Site du Zéro, je te donne le lien, je pense que la méthode peut t'intéresser. Je la trouve plus simple que la tienne :
http://www.siteduzero.com/forum-83 [...] bleau.html
Marsh Posté le 03-11-2011 à 16:20:30
Merci.
C'est plus ou moins ce que je faisais, sauf que tu sembles vouloir séparer les sites (j'avais cru que tu voulais les confondre), et qu'il y a une technique que je ne connaissais pas, car je ne l'avais jamais employée, et qui est super simple en effet [j'ai du relire trois fois le ++$h{$t[0]}{$t[1]}{$t[2]} pour que le déclic se fasse, et que je pense "Bon sang mais c'est bien sur..." ], la création de multiples hashs imbriqués. C'est du même style que ce que je faisais à la main, mais plus direct (en terme de lignes de code, avec ma création d'un tableau anonyme).
Je retiendrais dorénavant cette technique, car c'est assez pratique, ça simplifie des trucs que j'aurais fait à la main sinon.
A+,
Marsh Posté le 03-11-2011 à 18:59:46
En reprenant mon code, et en utilisant cette astuce:
Code :
|
C:\Perl>perl benji.pl benji.txt |
Ou benji.txt est
14/04/2011,titi,serveur1 |
A+,
Marsh Posté le 04-11-2011 à 09:33:48
C'est vrai que là, le code est plus court ! J'aime particulièrement ta ligne 33, où tu tries les dates en une seule ligne, alors que moi c'était toute une étape de trier les dates !
J'ai néanmoins plus simple à te suggérer pour remplacer les lignes 37 à 42 où tu testes l'existence de $myhash{$date}{$user}{$site}. Ne fais pas ce test, et print cette variable. Seulement, mets-là entre parenthèse et ajoute //0 à la fin. De cette façon, si elle n'existe pas, c'est la avleur 0 qui sera mise (enfin, pour toi, ce sera un tiret). Comme ceci :
Code :
|
Vu que tu as l'air intéressé, j'aimerais bien te remettre à contribution ! J'aimerais à présent faire le total des connections par mois et par serveur. Pour obtenir quelque chose comme ceci :
Citation : MOIS SERVEUR 1 SERVEUR 2 |
À la base, j'ai réussi à créer un fichier CSV de la forme :
Citation : Avril,utilisateura,SERVEUR1 |
Aurais-tu une petite idée pour procéder ? je pense qu'il faudrait réutiliser le principe de l'auto-vivification, mais je n'ai pas réussi à l'appliquer pour ce cas
Marsh Posté le 04-11-2011 à 10:36:51
Citation : J'ai néanmoins plus simple à te suggérer pour remplacer les lignes 37 à 42 où tu testes l'existence de $myhash{$date}{$user}{$site}. Ne fais pas ce test, et print cette variable. Seulement, mets-là entre parenthèse et ajoute //0 à la fin. |
J'aimerais bien savoir ou c'est documenté dans Perl, ça.
Parce que si tu veux pas un chiffre, est ce que ça marche encore avec une chaine, je vais tester
Pour le truc récapitulatif, inutile de changer de fichier je pense.
Je vais y jeter un œil.
EDIT: Oui, ca marche aussi avec du texte. On peut remplacer la fin de mon code précédent par
Code :
|
A+,
Marsh Posté le 04-11-2011 à 10:44:07
Je ne sais pas où c'est documenté :\ on m'a juste donné cette astuce sur un forum.
Si tu veux un chiffre en caractère par défaut, tu as juste à écrire ceci :
Code : |
Par contre, si tu veux une chaîne de caractère (ou un caractère), il faut préalablement faire un test :
Code :
|
Merci de chercher
EDIT : je viens de voir ton EDIT ^^ pour moi ça n'a pas marché de faire directement comme ça. Mais je concaténais la valeur avec une chaîne de caractère, apparemment y'a souvent des soucis avec la concaténation si on ne le fait pas comme il faut.
Marsh Posté le 04-11-2011 à 10:48:21
benji1000 a écrit : Je ne sais pas où c'est documenté :\ on m'a juste donné cette astuce sur un forum.
|
Pas la peine: avec
Code :
|
ça me donne
Date User serveur1 serveur2 serveur3 |
Bon, je regarde le reste.
A+,
Marsh Posté le 04-11-2011 à 11:16:33
Il suffisait d'ajouter un nouveau hash, et de faire
++$newhash{substr($date,3,2)}{$site};
On prend comme clé le mois et ensuite le site.
Note: ça suppose néanmoins que on ne fait ça que sur 12 mois, car ça ne se préoccupe pas de l'année (sinon, il faudrait faire quelque chose comme ++$newhash{substr($date,3,2)}{substr($date,-4)}{$site}; et tenir compte de l'année dans le code d'affichage)
Code :
|
C:\Perl>perl benji.pl benji.txt |
A+,
Marsh Posté le 04-11-2011 à 11:20:13
C'est bien ça, on ne se préoccupe pas de l'année. C'est juste pour voir quel serveur est le plus utilisé. J'essaie d'adapter ton code et je reviens te voir si j'ai un soucis. Merci bien
Marsh Posté le 04-11-2011 à 12:36:44
J'ai essayé d'adapter ton script au mien, mais ça ne fonctionne pas :\ aucun message d'erreur, mais ça n’affiche pas le tableau...
Code :
|
Les données sont stockées dans temp3.csv et j'ai des mois allant seulement de Avril à Septembre (mais après je pourrai rajouter d'autres mois, c'est pas ça le problème).
Concernant le HTML, <tr> c'est pour déclarer uen ligne et </tr> c'est pour la fermer. Pour les cases, c'est <td> et </td>.
Marsh Posté le 04-11-2011 à 12:47:24
Bon, je dois aller bouffer, je regarderais après.
Attention néanmoins:
Il faut impérativement:
my @months = qw{Decembre Janvier Fevrier Mars Avril Mai Juin Juillet Aout Septembre Octobre Novembre Decembre};
pour faire la correspondance valeur de $mois -> nom de mois (et le fait de démarrer à Décembre est pour que 1 corresponde a janvier).
La tu fais:
my @months = qw{Avril Mai Juin Juillet Aout Septembre};
ça peut pas marcher correctement car tu fais correspondre le mois 1 à Mai.
A+,
Marsh Posté le 04-11-2011 à 13:26:58
Bon, j'ai vu ta seconde erreur:
remplaces
print $html '<tr><td>'.$newhash{$month}.'</td><td>';
par
print $html '<tr><td>'.$months[$month].'</td><td>';
comme dans mon code
A+,
Marsh Posté le 04-11-2011 à 13:30:10
Pour l'opérateur //, j'ai trouvé l'info:
Citation : It's the defined-or operator that was added in Perl 5.10. It's like || |
Très utile pour initialiser une variable si elle ne l'est pas encore:
$mylang //= "en":
par exemple.
A+,
Marsh Posté le 04-11-2011 à 13:56:20
Merci de la précision
Sinon, pour le problème principal, ça ne fonctionne toujours pas, même en mettant tous les mois comme tu as fait :\ je ne vois pas d'où vient l'erreur, puisque j'ai quasiment repris ce que tu as fait, j'ai juste changé pour mettre au format HTML. Et je n'ai aucune erreur !
Marsh Posté le 04-11-2011 à 14:17:00
Mais chez moi, ça marche, hein...
En adaptant ton code pour sortir sur stdout, j'ai:
Code :
|
C:\Perl>perl benji.pl benji.txt |
Notes le s/\s*$//o; après le chomp, important, si tu ne veux pas avoir des sites différents a cause d'espaces qui trainent en fin de ligne.
Tu n'as pas changé le format du fichier de données?
Dans mes tests, benji.txt est
14/04/2011,titi,serveur1 |
A+,
Marsh Posté le 04-11-2011 à 14:44:00
Merci, voilà ce que j'ai comme erreurs :
Argument "" isn't numeric in array element at C:\chemin\script.pl line 203, <$temp3> line 836. |
La ligne 203 du script correspond à :
Code : |
Du coup, en résultat, dans la colonne "Mois", j'ai uniquement "Decembre" pour toutes les lignes ! Mais la fonction de comptage marche bien
Marsh Posté le 04-11-2011 à 14:52:50
clairement, la variable month récupérée comme clef de keys %newhash est incorrecte.
Il semble que ($date, $user, $site) = split ','; ne t'envoie pas la date au format dd/mm/yyyy dans $date
Donc comme je le disais, tes données n'ont pas changé de format?
A+,
Marsh Posté le 04-11-2011 à 15:04:54
Oui, suis-je bête ^^ j'avais déjà converti les dates avec les mois. Remarque, je te l'avais dit dans un de mes messages, tu n'as pas dû faire gaffe (j'avais déjà des données de la forme Avril,utilisateura,SERVEUR1). J'essaie de tout remettre comme il faut, ça devrait aller sans pb, et je te tiens au jus !
EDIT : au poil, merci beaucoup pour ton aide
Marsh Posté le 04-11-2011 à 15:07:00
Tu serais pas parti de ton csv avec les noms de mois au début?
Parce que ca expliquerait le "il" comme substr($date,3,2) sur avril, le ll comme substr($date,3,2) sur juillet, etc.
Je pensais que tu avais vu que je changeais pas de fichier de log, puisque c'était inutile.
EDIT: nos posts se sont croisés.
C'est en général plus couteux (espace disque...) de générer un fichier de log que d'exploiter un même fichier de log avec des scripts perls adaptés au résultat recherché.
A+,
Marsh Posté le 04-11-2011 à 15:09:02
Yep, c'est bien ça j'ai pas un grand niveau, donc des opérations que tu effectues rapidement, je mets souvent plusieurs lignes de codes à les faire, pour pas grand chose au final ^^
Marsh Posté le 04-11-2011 à 15:13:02
C'est juste une question d'expérience.
Plus on fait de scripts perl, plus on s’aperçoit que c'est pratique à utiliser.
Bon, quand Perl6 sera un jour utilisable, ce sera encore plus puissant (mais il y a diverses habitudes d'écriture qui auront changé et auquel il faudra s'adapter).
A+,
Marsh Posté le 28-10-2011 à 15:21:57
Bonjour,
j'aimerais créer un script permettant de compter le nombre de doublons d'un tableau pour chaque cas. Par exemple, j'ai un CSV du type suivant (que je mets dans un Array) :
14/04/2011,titi,serveur1
14/04/2011,titi,serveur2
14/04/2011,titi,serveur1
14/04/2011,titi,serveur1
15/04/2011,titi,serveur2
15/04/2011,titi,serveur1
15/04/2011,titi,serveur2
15/04/2011,titi,serveur1
15/04/2011,titi,serveur2
15/04/2011,titi,serveur2
18/04/2011,titi,serveur2
18/04/2011,titi,serveur1
18/04/2011,titi,serveur1
18/04/2011,titi,serveur1
J'ai déjà cette méthode qui permet de supprimer les doublons :
Mon but final est d'obtenir un tableau qui récapitule le nombre de fois qu'un utilisateur s'est connecté au serveur 1 ou au serveur 2, par jour.
Bien sûr, dans mon fichier, je n'ai pas seulement l'utilisateur "titi" (j'ai en revanche uniquement 2 serveurs). Pourriez-vous me donner des conseils pour atteindre mon but ? Pas du code tout mâché, mais un algo ou la méthode pour procéder. Peut-être que la méthode "compter le nombre de doublons pour chaque cas, puis supprimer les doublons" que j'ai choisi n'est pas la meilleure...
Je précise que je ne suis pas très expérimenté en Perl, donc n'hésitez pas à détailler votre pensée. Merci d'avance pour vos réponses !
---------------
www.benji1000.net