在perl中对目录进行排序,将数字考虑在内

时间:2010-06-02 18:36:04

标签: perl sorting readdir

我认为我需要某种Schwartzian Transform来实现这一点,但我无法弄清楚,因为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;
    }
}

6 个答案:

答案 0 :(得分: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

答案 1 :(得分: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;

另外,请查看File::Next模块。它会让你只挑选以“album”开头的文件。我发现它比 readdir 更容易。

答案 2 :(得分: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变换解决了一个不同的问题:它阻止你必须运行一个复杂的任务(不像你需要的那个 - 一个正则表达式)多个搜索算法中的时间。它需要缓存结果的内存成本(不是出乎意料)。基本上,您所做的是将问题的输入映射到输出(通常在数组中)[$input, $output],然后对输出$a->[1] <=> $b->[1]进行排序。现在您的东西已经分类,您可以重新映射以获取原始输入$_->[0]

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

很酷,因为它非常紧凑而且效率很高。

答案 3 :(得分:1)

在这里,使用Schwartzian变换:

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

答案 4 :(得分: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

答案 5 :(得分: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
      ;