我认为我需要某种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;
}
}
答案 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
;