2010-06-02 11 views
1

Je pense que j'ai besoin d'une sorte de Schwartzian Transform pour que cela fonctionne, mais j'ai du mal à le comprendre, car perl n'est pas mon langage le plus fort.Trier un répertoire dans perl, en prenant en compte les nombres

J'ai un répertoire avec le contenu en tant que tel:

album1.htm 
album2.htm 
album3.htm 
.... 
album99.htm 
album100.htm 

Je suis en train de faire l'album avec le plus grand nombre de ce répertoire (dans ce cas, album100.htm). Notez que les horodatages sur les fichiers ne sont pas un moyen fiable de déterminer les choses, car les gens ajoutent de vieux albums "manquants" après le fait.

Le développeur précédent a simplement utilisé l'extrait de code ci-dessous, mais cela se décompose clairement lorsqu'il y a plus de 9 albums dans un répertoire.

opendir(DIR, PATH) || print $!; 
@files = readdir(DIR); 
foreach $file (sort(@files)) { 
    if ($file =~ /album/) { 
     $last_file = $file; 
    } 
} 

Répondre

7

Si vous avez juste besoin de trouver l'album avec le plus grand nombre, vous avez vraiment pas besoin de trier la liste, il suffit d'exécuter à travers elle et de garder trace du maximum.

#!/usr/bin/perl 

use strict; 
use warnings; 

my $max = 0; 

while (<DATA>) { 
    my ($album) = $_ =~ m/album(\d+)/; 
    $max = $album if $album > $max; 
} 

print "album$max.htm"; 

__DATA__ 
album1.htm 
album100.htm 
album2.htm 
album3.htm 
album99.htm 
3

Pour trouver le plus grand nombre, essayez un tri personnalisé ...

sub sort_files { 
    (my $num_a = $a) =~ s/^album(\d+)\.htm$/$1/; 
    (my $num_b = $b) =~ s/^album(\d+)\.htm$/$1/; 
    return $num_a <=> $num_b; 
} 

my @sorted = sort \&sort_files @files; 
my $last = pop @sorted; 

En outre, un coup d'oeil sur le module . Il vous laissera choisir seulement les fichiers qui commencent par le mot "album". Je trouve cela un peu plus facile que readdir.

2

La raison pour laquelle vous rencontrez des difficultés est l'opérateur, <=> est la comparaison numérique, cmp est la valeur par défaut et il est comparaison de chaînes.

$ perl -E'say for sort qw/01 1 02 200/'; 
01 
02 
1 
200 

Avec une légère modification nous obtenons quelque chose de beaucoup plus proche de corriger:

$ perl -E'say for sort { $a <=> $b } qw/01 1 02 200/'; 
01 
1 
02 
200 

Cependant, dans votre cas, vous devez supprimer les chiffres non.

$ perl -E'say for sort { my $s1 = $a =~ m/(\d+)/; my $s2 = $b =~ /(\d+)/; $s1 <=> $s2 } qw/01 1 02 200/'; 
01 
1 
02 
200 

est ici plus jolie:

sort { 
    my $s1 = $a =~ m/(\d+)/; 
    my $s2 = $b =~ /(\d+)/; 
    $s1 <=> $s2 
} 

Ce n'est pas parfait, mais il devrait vous donner une bonne idée de votre problème avec tri.

Oh, et comme le suivi, la Shcwartzian Transformer résout un problème différent: il vous empêche d'avoir à exécuter une tâche complexe (contrairement à celui que vous besoin - une expression régulière) plusieurs fois dans la recherche algorithme. Il vient à un coût de la mémoire d'avoir à mettre en cache les résultats (ne pas être inattendu). Essentiellement, ce que vous faites est de cartographier l'entrée du problème, à la sortie (généralement dans un tableau) [$input, $output] puis vous trier sur les sorties $a->[1] <=> $b->[1]. Avec votre matériel maintenant trié, vous retrouvez vos entrées d'origine $_->[0].

map $_->[0], 
sort { $a->[1] <=> $b->[1] } 
map [ $_, fn($_) ] 
, qw/input list here/ 
; 

Il est cool parce qu'il est si compact tout en étant si efficace.

1

Ici, vous allez, en utilisant Transformée Schwartzienne:

my @files = <DATA>; 

print join '', 
    map { $_->[1] } 
    sort { $a->[0] <=> $b->[0] } 
    map { [ m/album(\d+)/, $_ ] } 
    @files; 


__DATA__ 
album12.htm 
album1.htm 
album2.htm 
album10.htm 
1

est ici une solution de rechange à l'aide reduce:

use strict; 
use warnings; 
use List::Util 'reduce'; 

my $max = reduce { 
    my ($aval, $bval) = ($a =~ m/album(\d+)/, $b =~ m/album(\d+)/); 
    $aval > $bval ? $a : $b 
} <DATA>; 
print "max album is $max\n"; 

__DATA__ 
album1.htm 
album100.htm 
album2.htm 
album3.htm 
album99.htm 
1

Voici une solution générique:

my @sorted_list 
    = map { $_->[0] } # we stored it at the head of the list, so we can pull it out 
     sort { 
      # first test a normalized version 
      my $v = $a->[1] cmp $b->[1]; 
      return $v if $v; 

      my $lim = @$a > @$b ? @$a : @$b; 

      # we alternate between ascii sections and numeric 
      for (my $i = 2; $i < $lim; $i++) { 
       $v = ($a->[$i] || '') cmp ($b->[$i] || ''); 
       return $v if $v; 

       $i++; 
       $v = ($a->[$i] || 0) <=> ($b->[$i] || 0); 
       return $v if $v; 
      } 
      return 0; 

     } 
     map { 
      # split on digits and retain captures in place. 
      my @parts = split /(\d+)/; 
      my $nstr = join('', map { m/\D/ ? $_ : '0' x length() } @parts); 
      [ $_, $nstr, @parts ]; 
     } @directory_names 
     ;