[couverture de Linux Magazine 68]

Perles de Mongueurs (9)

Article publié dans Linux Magazine 68, janvier 2005.

[+ del.icio.us] [+ Developers Zone] [+ Bookmarks.fr] [Digg this] [+ My Yahoo!]

Petits unilignes entre amis

Supprimer des doublons

Un de mes amis (webmestre de http://www.fatrazie.com/) possède un fichier avec près de 50 000 noms de villes françaises avec leurs coordonnées géographiques et leurs codes postaux. Ce fichier a été lui-même assemblé laborieusement à partir de diverses sources et à l'aide de programmes Perl (dont le module WWW::Gazetteer::HeavensAbove, que j'ai écrit pour l'occasion).

Le fichier courant contient une ville par ligne, avec dans l'ordre les champs nom, latitude, longitude, élévation et code postal, séparés par des tabulations. En voici un extrait :

    Montluel	45.850	5.050	195	01120
    Nièvroz	45.833	5.067	185	01120
    Pizay	45.883	5.083	284	01120
    Pizay	45.733	4.333	492	01120
    Thil	45.817	5.017	182	01120
    Sainte-Croix	44.767	5.283	425	01120
    Sainte-Croix	45.900	5.050	280	01120
    Sainte-Croix	44.767	5.283	425	01120
    La Léchere	45.200	6.467	1075	01121
    La Léchère	45.867	5.100	238	01121
    La Léchère	45.867	5.100	238	01121
    Léchère	45.583	6.333	1393	01121
    Belleydoux	46.250	5.767	754	01130
    Charix	46.183	5.683	758	01130

Pour « nettoyer » son fichier, il souhaite maintenant supprimer les doublons de villes ayant le même nom et le même code postal (les coordonnées géographiques sont souvent très proches, voire identiques).

L'objectif de cette perle n'est pas seulement de vous montrer l'uniligne qui a fait tout le travail, mais surtout de vous apprendre le réflexe presque pavlovien de tout perleur accompli : quand vous entendez le mot « unique », vous devez immédiatement penser « table de hachage ». Ensuite, tout le problème est de construire la bonne clé pour ce hachage.

Dans le cas qui nous occupe, c'est tout simple : on considère que deux villes sont identiques si elles ont le même nom et le même code postal. Notre clé sera donc la simple concaténation de ces deux champs.

Ce qui donne l'uniligne suivant :

    $ perl -lnaF\\t -e 'print unless $c{$F[0].$F[-1]}++' FranceA-Z.txt > FranceA-unique.txt

Les options -a et -F sont expliquées dans perlrun(1) ainsi que dans mon articles Écrire un programme d'une ligne en Perl, publié dans Linux Magazine 50 et repris dans Linux Dossiers 2. Vous savez donc que le tableau @F contient les champs de la ligne en cours.

Notez que le caractère qui suit immédiatement l'option -F est une tabulation, car c'est le caractère de séparation utilisé dans le fichier. Il est protégé du shell par un \.

L'utilisation de l'opérateur ++ postfixé fait que la première fois qu'on rencontre une ligne produisant une certaine clé, la valeur dans la table de hachage %c est auto-vivifiée à undef (donc fausse). Le print() est exécuté et la valeur est ensuite mise à 1.

Et ça marche !

    $ wc -l France*
      50049 FranceA-Z.txt
      35823 FranceA-Z-unique.txt
    $ diff FranceA-Z.txt FranceA-Z-unique.txt
    ...
     Montluel	45.850	5.050	195	01120
     Nièvroz	45.833	5.067	185	01120
     Pizay	45.883	5.083	284	01120
    -Pizay	45.733	4.333	492	01120
     Thil	45.817	5.017	182	01120
     Sainte-Croix	44.767	5.283	425	01120
    -Sainte-Croix	45.900	5.050	280	01120
    -Sainte-Croix	44.767	5.283	425	01120
     La Léchere	45.200	6.467	1075	01121
     La Léchère	45.867	5.100	238	01121
    -La Léchère	45.867	5.100	238	01121
     Léchère	45.583	6.333	1393	01121
     Belleydoux	46.250	5.767	754	01130
     Charix	46.183	5.683	758	01130
    ...

Attention quand vous utilisez des clés composites : contrairement au cas ci-dessus, il est en général préférable d'utiliser un séparateur spécifique entre ces clés. Cela permet d'éviter des collisions fâcheuses, par exemple avec des cas où une clé serait la concaténation de ab, a et l'autre celle de a et ba.

Le problème ne se posait pas dans notre cas, car il n'existe pas de ville dont le nom se termine par un nombre dans notre fichier.

Pour nous simplifier la vie, nous allons utiliser une technique remontant à Perl 4 : l'émulation de tableaux multi-dimensionnels (à l'époque, les références n'existaient pas et c'était la seule manière de faire des tableaux multi-dimensionnels). Cela consiste à séparer les différents éléments de la clé par des virgules.

Notre uniligne deviendrait (on a changé le . en ,) :

    $ perl -lnaF\\t -e 'print unless $c{$F[0],$F[-1]}++' FranceA-Z.txt > FranceA-unique.txt

Perl remplace alors $c{$F[0],$F[-1]} par $c{join $;, $F[0], $F[-1]}, comme expliqué dans perlvar(1) à la section parlant de la variable $;. Par défaut, $; est le caractère \034, qui a tout de même peu de chances de se retrouver dans vos données.

(Philippe "BooK" Bruhat, Paris.pm & Lyon.pm - book@mongueurs.net)

Calculer un handle de fichier

J'ai récemment dû faire le tri entre les « bonnes » lignes et les « mauvaises » lignes d'un fichier. Le fichier en question était la sortie de comm(1). Il s'agissait de vérifier que toutes les lignes d'un fichier A étaient présentes dans le fichier B (A et B étant triés).

On utilise donc comm -2 A B pour obtenir les lignes de A absentes de B et les lignes de A présentes dans B. Ces dernières sont précédées d'une tabulation puisque comm(1) présente les résultats en colonnes.

Pour distribuer les lignes dans les fichier A_ok et A_err, on utilise l'uniligne suivant :

    comm -2 A B | perl -nle 'print{s/^\t//?STDOUT:STDERR}$_' > A_ok 2> A_err

Explication : on utilise l'opérateur ternaire ?: pour choisir vers quel filehandle écrire la ligne courante : la sortie standard ou la sortie d'erreur. Le choix est conditionné par la présence d'une tabulation en début de ligne, que l'on enlève au passage (s/^\t//). Le filehandle donné à print doit être soit un mot simple (bareword), soit une variable scalaire (sinon l'analyseur syntaxique de Perl n'arrive pas à s'y retrouver). Toute chose plus compliquée que cela (comme un élément de tableau ou une expression) doit être placée entre accolades :

    print { expression qui renvoie un filehandle } ...

Ensuite, on utilise le shell pour rediriger la sortie standard et la sortie d'erreur vers deux fichiers différents.

(Cédric Bouvier, Genève.pm - cbouvi@mongueurs.net)

Intégrer des modules non-publics dans un mini-CPAN

Maintenant que vous avez votre mini-CPAN (et qu'il est partagé afin d'en faire profiter vos collègues), vous aimeriez y ajouter les modules développés en interne et qui ne peuvent pas être publiés. L'intérêt est de pouvoir les installer via la commande cpan comme n'importe quel autre module public. Remerciez Shawn Sorichetti d'avoir écrit CPAN::Mini::Inject dont c'est justement le rôle. La commande mcpani, installée avec le module, permet de gérer son mini-CPAN encore plus simplement via un fichier de configuration (que mcpani ira chercher dans $HOME/.mcpani/config, /usr/local/etc/mcpani ou /etc/mcpani) :

    local: /usr/local/cpan
    remote: http://your.cpan-mirror.net/ ftp://ftp.cpan.org/pub/CPAN/
    repository: /work/perl/modules
    passive: yes

Les paramètres parlent d'eux-mêmes : local indique le répertoire où est stocké le mini-CPAN, remote les sites sur lesquels se synchroniser et repository le répertoire contenant les modules privés. passive indique d'utiliser le mode passif lors des transferts FTP.

L'intégration de modules privés passent d'abord par leur ajout dans le repository :

    $ mcpani --add --module My::Great::Module --authorid JSUIFER \
        --modversion 0.01 --file /home/jean/perl/My-Great-Module-0.01.tar.gz

--module précise le nom du module à ajouter, --modversion sa version et --file l'archive, dont le nom doit respecter les conventions du CPAN. --authorid précise l'identifiant CPAN de l'auteur, mais qui n'a pas besoin d'exister officiellement dans ce cas-ci. (CPAN::Mini::Inject réalise ici une opération similaire à ce que fait PAUSE lors de l'indexation d'un module déposé sur le CPAN.)

Les modules ajoutés à ce repository sont ensuite injectés dans le mini-CPAN par la commande :

    $ mcpani --inject

Pour maintenir à jour votre mini-CPAN, il vous suffira d'exécuter mcpani avec l'option --update qui met d'abord à jour le mini-CPAN puis réinjecte les modules non-publics du repository.

(Sébastien Aperghis-Tramoni, Marseille.pm - <sebastien@aperghis.net>)

Le saviez-vous ?

Perl Advent Calendar

L'avent. Après.

Pour les anglophones, mentionnons (certes un peu tard) le calendrier de l'avent offert chaque année depuis l'an 2000 à la communauté Perl par Mark Fowler, le leader de London.pm : http://perladvent.org/. Chaque jour de l'avent, il y présente un module de CPAN, avec quelques explications.

Mark a donc remis ça en cette année 2004 et les premiers modules présentés furent : DateTime, Term::ANSIColor, Class::Accessor::Chained, String::ShellQuote, CPAN::Mini, Module::Pluggable, Term::ProgressBar...

Consultez le site pour voir la liste complète (ainsi que les listes des années précédentes) et découvrir des modules que vous ne connaissiez peut-être pas !

(Stéphane Payrard, Paris.pm - <stef@mongueurs.net>)

À vous !

Envoyez vos perles à perles@mongueurs.net, elles seront peut-être publiées dans un prochain numéro de Linux Magazine.

[IE7, par Dean Edwards] [Validation du HTML] [Validation du CSS]