2010-06-02 2 views
1

Мне кажется, мне нужно что-то вроде Schwartzian Transform, чтобы получить эту работу, но мне сложно разобраться, поскольку perl не самый сильный язык.Сортировка каталога в perl, с учетом номеров

У меня есть каталог с содержимым, как например:

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

Я пытаюсь получить альбом с наибольшим числом из этого каталога (в данном случае, album100.htm). Обратите внимание, что временные метки файлов не являются надежным средством определения вещей, поскольку люди добавляют старые «отсутствующие» альбомы после факта.

Предыдущий разработчик просто использовал фрагмент кода ниже, но это четко ломается, когда в каталоге находится более 9 альбомов.

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

ответ

7

Если вам просто нужно найти альбом с наибольшим числом, вы действительно не нужно, чтобы отсортировать список, просто запустить через него и следить за максимумом.

#!/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

Чтобы найти наибольшее число, попробуйте пользовательский вид ...

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; 

Кроме того, обратите внимание на модуле. Это позволит вам выбрать только файлы, которые начинаются со слова «альбом». Мне это немного легче, чем readdir.

2

Причина, почему вы сталкиваетесь с трудностями, является оператором, <=> является сравнением чисел, cmp является по умолчанию и это сравнение строк.

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

С небольшими изменениями мы получаем что-то гораздо ближе, чтобы исправить:

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

Однако в вашем случае вам необходимо удалить НЕРАСПРОСТРАНЕНИИ цифры.

$ 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 

Вот это более милый:

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

Это не безупречно, но это должно дать вам хорошее представление о вашей проблеме с родом.

О, и как следовать вверх, Shcwartzian Transform решает другую проблему: он останавливает вас от того, чтобы выполнить сложную задачу (в отличие от одного вы нуждающегося - регулярное выражение) несколько раз в поиске алгоритм. Это связано с необходимостью кэширования результатов (не для того, чтобы быть неожиданным). По существу, то, что вы делаете, сопоставляет ввод проблемы, к выходу (обычно в массиве) [$input, $output], тогда вы сортируете на выходах $a->[1] <=> $b->[1]. Теперь, когда ваши материалы отсортированы, вы вернетесь назад, чтобы получить исходные входы $_->[0].

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

Это классно, потому что он настолько компактен, будучи настолько эффективным.

1

Здесь вы идете, используя Шварца Transform:

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

Вот альтернативное решение с использованием 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

Вот общее решение:

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 
     ; 
Смежные вопросы