2009-07-27 7 views
2

J'ai une liste de dates jj/mm/aaaa stockées dans un hachage que j'ai besoin d'imprimer séquentiellement dans l'ordre de la date par mois. Voici l'extrait pertinent.Comment trier une date au format jj/mm/aaaa par mois en utilisant Perl?

use feature 'say'; 

for my $date (sort {$a cmp $b} keys %data) { 
    say $date; 
} 

Ce sorties:

16/07/2008 
16/08/2008 
16/09/2008 
17/07/2008 
17/08/2008 
17/09/2008, etc. 

quand ce que je dois est:

16/07/2008 
17/07/2008 
16/08/2008 
17/08/2008 
16/09/2008 
16/09/2008, etc. 

Comment puis-je y parvenir?

Répondre

7

Si le format est un 10 caractères rigide:

sort { substr($a,3,2) cmp substr($b,3,2) } @dates; 
+0

J'aime la brièveté! – Zaid

+0

Vous devriez faire la Transformation Schwartzienne pour ne pas avoir à recalculer deux fois les parties comparables pour chaque élément. Surtout si le format n'est pas rigide. – dlamblin

+3

L'optimisation prématurée est la racine de tous les maux. Ne pensez-vous pas que "substr" va être aussi rapide ou plus rapide qu'une recherche de hash? – jrockway

7

Parse et tri:

sub get_month { 
    my $date = shift; 
    my ($d, $m, $y) = split m{/}, $date; 
    return $m; 
} 

sort { get_month($a) <=> get_month($b) } @dates; 

J'utiliserions probablement DateTime, cependant, puisque je veux travailler avec des objets, et non des chaînes vides de sens:

my $parser = DateTime::Format::Natural->new(format => 'dd/mm/yyyy'); 
sort map { $parser->parse_datetime($_) } @dates; 
+1

Sucrée. On peut donc intégrer des sous-routines dans la commande de tri. Je ne savais pas que ... – Zaid

+0

"dans l'ordre de la date par mois" - Je supposais que cela signifiait tri par jour avec un mois, et que "par mois" signifiait en fait par mois dans l'année. – ysth

+0

@ysth: C'est vrai. La solution de jrockway ne fait pas ça, mais elle m'a orienté dans la bonne direction. – Zaid

12

Vous pouvez le faire en utilisant un Schwartzian Transform, comme si:

for my $date(map { $_->[0] } 
       sort { $a->[1] <=> $b->[1] } 
       map { [ $_, (split /\//, $_)[1] ] } 
       keys %data) { 
    ... 

Cela prend chaque clé de %data et le met dans un tableau anonyme où le premier élément est la clé et le second est le champ du milieu de la scission sur /. Ensuite, il trie par le second élément du tableau, et utilise un autre map pour obtenir la liste originale des clés.

2

De la réponse de perlfaq4 à How do I sort an array by (anything)?


offre une fonction de comparaison pour trier() (décrit dans une sorte dans perlfunc):

@list = sort { $a <=> $b } @list; 

La fonction de tri par défaut est cmp, comparaison de chaînes, w qui trierait (1, 2, 10) en (1, 10, 2). < =>, utilisé ci-dessus, est l'opérateur de comparaison numérique.

Si vous avez besoin d'une fonction compliquée pour extraire la pièce que vous voulez trier, ne le faites pas dans la fonction de tri. Tirez-le d'abord, car le type BLOCK peut être appelé plusieurs fois pour le même élément. Voici un exemple de la façon de retirer le premier mot après le premier nombre sur chaque élément, puis de trier ces mots sans tenir compte de la casse.

@idx =(); 
for (@data) { 
    ($item) = /\d+\s*(\S+)/; 
    push @idx, uc($item); 
    } 
@sorted = @data[ sort { $idx[$a] cmp $idx[$b] } 0 .. $#idx ]; 

qui pourrait aussi être écrit de cette façon, en utilisant un truc qui est d'être connu comme Transformée Schwartzienne:

@sorted = map { $_->[0] } 
    sort { $a->[1] cmp $b->[1] } 
    map { [ $_, uc((/\d+\s*(\S+)/)[0]) ] } @data; 

Si vous devez trier sur plusieurs champs, le paradigme suivant est utile.

@sorted = sort { 
    field1($a) <=> field1($b) || 
    field2($a) cmp field2($b) || 
    field3($a) cmp field3($b) 
    } @data; 

Ceci peut être commodément combiné avec le précalcul des clés comme indiqué ci-dessus.Voir l'article de tri dans la collection «Bien plus que vous ne l'avez jamais voulu savoir» au http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz pour en savoir plus sur cette approche.

Voir aussi la question plus loin dans perlfaq4 sur les hachages de tri.

Questions connexes