2009-07-27 6 views
2

Ich habe eine Liste von TT/MM/JJJJ-Daten in einem Hash gespeichert, die ich nacheinander in der Reihenfolge von Datum für Monat ausdrucken muss. Hier ist der relevante Auszug.Wie kann ich ein TT/MM/JJJJ-formatiertes Datum für Monat mit Perl sortieren?

use feature 'say'; 

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

Diese Ausgänge:

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

wenn das, was ich brauche, ist:

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

Wie kann ich das erreichen?

Antwort

7

Wenn das Format ein starres 10 Zeichen:

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

Ich mag die Kürze! – Zaid

+0

Sie sollten jedoch die Schwartzsche Transformation durchführen, damit Sie die vergleichbaren Teile nicht zweimal für jedes Element neu berechnen müssen. Vor allem, wenn das Format nicht starr ist. – dlamblin

+3

Vorzeitige Optimierung ist die Wurzel allen Übels. Meinst du nicht, dass "substr" so schnell oder schneller sein wird wie ein Hash-Lookup? – jrockway

7

Parse und sort:

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

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

ich wahrscheinlich Datetime verwenden würde, aber da ich mit Objekten arbeiten möchten, nicht sinnlos Strings:

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

Süß. So kann man Unterroutinen in den Sortierbefehl einbetten. Ich wusste nicht, dass ... – Zaid

+0

"in der Reihenfolge von Datum für Monat" - ich nahm an, dass bedeutete, nach Tag innerhalb des Monats zu sortieren, und das "nach Monat" eigentlich von Monat zu Jahr bedeutet. – ysth

+0

@ysth: Das stimmt. Die Lösung von jrockway macht das nicht, aber sie hat mich in die richtige Richtung gelenkt. – Zaid

12

Sie eine Schwartzian Transform tun können, verwenden, etwa so:

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

Dies nimmt jede Taste von %data und legt es in einem anonymen Array ab, wobei das erste Element der Schlüssel und das zweite das mittlere Feld von / ist. Dann sortiert es nach dem zweiten Element des Arrays und verwendet eine andere map, um die ursprüngliche Liste der Schlüssel zu erhalten.

2

Von perlfaq4 ‚s Antwort auf How do I sort an array by (anything)?


Versorgung eine Vergleichsfunktion zu sortieren() (in Art in perlfunc beschrieben):

Die Standard-Sortierfunktion ist cmp, String-Vergleich, w das würde sortieren (1, 2, 10) in (1, 10, 2). < =>, oben verwendet, ist der numerische Vergleichsoperator.

Wenn Sie eine komplizierte Funktion benötigen, um den zu sortierenden Teil herauszuziehen, tun Sie es nicht innerhalb der Sortierfunktion. Ziehen Sie es zuerst heraus, weil der Sortierblock mehrmals für dasselbe Element aufgerufen werden kann. Hier ist ein Beispiel, wie man das erste Wort nach der ersten Zahl an jedem Element herauszieht und diese Wörter dann fallunabhängig sortiert.

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

, die auch auf diese Weise geschrieben werden konnten, einen Trick verwenden, die bekannt sein gekommen ist als die Schwartzian Transformation:

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

Wenn Sie auf mehreren Feldern sortieren müssen, ist das folgende Paradigma nützlich.

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

Dies kann bequem mit Vorberechnung der Schlüssel wie oben angegeben kombiniert werden.

Siehe Art Artikel in der „weit mehr als Sie jemals wissen wollten“ diesen Ansatz in http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz für mehr Sammlung.

Siehe auch die Frage später in perlfaq4 auf Sortierung Hashes.

Verwandte Themen