使用perl进行高级排序

时间:2013-12-11 12:38:31

标签: perl sorting

我想使用Perl以下列方式对文件中的条目进行排序:

0filename
01filename
0111filename
0112filename
0113filename
02filename
021filename
022filename
1filename
11filename
110filename
1101filenam
2filename
21filename
211filename
212filename

我在perl中使用了sort函数,现在我获取值的方式是:

0111filename
0112filename
0113filename
01filename
021filename
022filename
02filename
0filename
11filename
110filename
1101filename
1filename
211filename
212filename
21filename
2filename

我能够得出结论,在涉及2filename的情况下它以某种方式取得文件名的第一个字符的ascii值,因此如果情况是2file1那么它将2f与21进行比较,因此结果错误!!

我很久以来就坚持这个......任何帮助都会受到高度赞赏!!!!

5 个答案:

答案 0 :(得分:4)

使用Schwartzian transform

排序
use File::Slurp;

my @sorted =
  map $_->[0],
  sort { $a->[1] cmp $b->[1] }   # string sort
  # sort { $a->[1] <=> $b->[1] } # numsort
  map [ $_, /^(\d+)/ ],
  read_file("file");

print @sorted;

答案 1 :(得分:3)

领先零是有问题的。每个人(包括我之前的回答)都使用了字符串数字部分的数字比较,但是考虑了123 == 0123,你不这样做。

解决方案是对数字部分进行字符串比较。

my @sorted = sort {
   my ($a_num) = $a =~ /^(\d+)/;
   my ($b_num) = $b =~ /^(\d+)/;
   $a_num cmp $b_num
} @data;

如果你有Perl 5.14,那么下面的速度会快一些。

my @sorted = sort {
   ( $a =~ s/(?!\d)/\0/r ) cmp ( $b =~ s/(?!\d)/\0/r )
} @data;

切换到Schwartzian变换可以获得更多收益。

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

以下就地排序更快。

s/\0// for @data;
@data = sort @data;
s/(?!\d)/\0/ for @data;

以下版本与就地版本一样快,但需要5.14。

my @sorted =
   map s/\0//r,
   sort
   map s/(?!\d)/\0/r,
   @data;

基准测试结果:

            Rate    naive naive514       st   grt514  inplace
naive    14775/s       --     -12%     -55%     -70%     -70%
naive514 16775/s      14%       --     -49%     -66%     -66%
st       32630/s     121%      95%       --     -33%     -34%
grt514   48713/s     230%     190%      49%       --      -1%
inplace  49211/s     233%     193%      51%       1%       --

基准代码:

use strict;
use warnings;
use feature qw( say );

use Benchmark  qw( cmpthese );
use List::Util qw( shuffle );

sub naive {
   my @sorted = sort {
      my ($a_num) = $a =~ /^(\d+)/;
      my ($b_num) = $b =~ /^(\d+)/;
      $a_num cmp $b_num
   } @_;
   1;
}

sub naive514 {
   my @sorted = sort {
      ( $a =~ s/(?!\d)/\0/r ) cmp ( $b =~ s/(?!\d)/\0/r )
   } @_;
   1;
}

sub st {
   my @sorted =
      map $_->[0],
      sort { $a->[1] cmp $b->[1] }
      map [ $_, /^(\d+)/ ],
      @_;
   1;
}

sub grt514 {
   my @sorted =
      map s/\0//r,
      sort
      map s/(?!\d)/\0/r,
      @_;
   1;
}

sub inplace {
   s/\0// for @_;
   @_ = sort @_;
   s/(?!\d)/\0/ for @_;
   1;
}

my @data = shuffle qw(
   0filename
   01filename
   0111filename
   0112filename
   0113filename
   02filename
   021filename
   022filename
   1filename
   11filename
   110filename
   1101filename
   2filename
   21filename
   211filename
   212filename
);

cmpthese(-3, {
   naive    => sub { naive    @data },
   naive514 => sub { naive514 @data },
   st       => sub { st       @data },
   grt514   => sub { grt514   @data },
   inplace  => sub { inplace  @data },
});

答案 2 :(得分:2)

perl sort函数可以与使用的提供的比较函数一起使用。

第一个参数应该是函数或代码块。要比较的变量存储在参数$ a和$ b中。

例如,要排序为字符串(而不是数字):

sort {$a cmp $b} @list;

sort perldoc

中查看更多详情

答案 3 :(得分:1)

您希望能够按数字前缀对文件进行排序,然后按文件名排序?

默认情况下,Perl按ASCII字母序列 1 对数组进行排序。

如果这不能满足您的需求,Perl允许您编写自己的比较例程。 Perl将这两个项目放在变量$a$b中。您的工作是弄清楚如何比较$a$b。在您的情况下,您希望将字符串名称拆分为单独的数字和字符串部分,然后比较每个部分。

Sort使用两个特殊运算符:<=>用于数字比较,cmp用于字符串比较。这是一个简单的例子:

use strict;
use warnings;
use feature qw(say);

my @array = <DATA>;
chomp @array;

say join "\n", sort( sort_array @array ); # Parens not necessary, but make it easier to read

sub sort_array {
    my ( $a_num, $a_name ) = $a =~ /^(\d+)(.*)/;
    my ( $b_num, $b_name ) = $b =~ /^(\d+)(.*)/;

    if ( $a_num != $b_num ) {
        return $a_num <=> $b_num;
    }
    return $a_name cmp $b_name;
}

__DATA__
0111filename
0112filename
0113filename
01filename
021filename
022filename
02filename
0filename
11filename
110filename
1101filename
1filename
211filename
212filename
21filename
2filename

我的子程序传递了两个值$a$b。我不确切知道它们是什么,但它们是我阵列中的两个值。

然后我将$a$b分成我文件名的数字和字符串部分。然后我比较数字部分。如果数字部分不匹配,则更高的数字部分将在稍后进行排序。如果数字部分相等,我必须使用cmp比较该名称的字符串部分。如果这些也相等怎么办?这意味着$a$b相同,所以我不在乎 - 只是传递结果。

根据您的数据,您可能需要更多检查,例如文件与模式/^(\d+)(*.)/不匹配的内容。 (你可能只想要一个字符串排序)。如果名称中没有字符串部分怎么办? (也就是说,在模式匹配后,$a_name$b_name未定义。

您还可以在sort语句中嵌入排序例程。优点:您可以在使用它的地方找到排序声明。缺点:如果它真的很长,它可能会使你的程序更难理解。

换句话说,我本可以这样做:

say join ( "\n", sort  {
    my ( $a_num, $a_name ) = $a =~ /^(\d+)(.*)/;
    my ( $b_num, $b_name ) = $b =~ /^(\d+)(.*)/;

    if ( $a_num != $b_num ) {
        return $a_num <=> $b_num;
    }
    return $a_name cmp $b_name;
} @array );

在我的sort语句中嵌入我的sort子例程。您必须确定这是否会使您的程序更容易理解。

查看sort功能的文档。它将为您提供一些很好的示例,并帮助您了解它的工作原理。


OOPS:不太对......

正如池上所指出的那样。之前没有返回您想要的排序。我误解了你想要排序的东西。我以为你是按数字顺序排序文件名的数字部分,然后按文件名排序非数字顺序。

但是,您似乎还要通过字符串比较顺序对数字前缀进行排序。那是02323filename10filename之前,因为02323应该在10之前,即使2,323是一个更大的数字。

没什么大不了的。您只需将前一个示例(<=>)中的数字比较运算符更改为字符串比较(cmp):

say join ( "\n", sort  {
    my ( $a_num, $a_name ) = $a =~ /^(\d+)(.*)/;
    my ( $b_num, $b_name ) = $b =~ /^(\d+)(.*)/;

    if ( $a_num != $b_num ) {
        return $a_num cmp $b_num;
    }
    return $a_name cmp $b_name;
} @array );

现在返回:

0filename
01filename
0111filename
0112filename
0113filename
02filename
021filename
022filename
1filename
11filename
110filename
1101filename
2filename
21filename
211filename
212filename

返回上一个排序(使用数字比较):

0filename
01filename
1filename
02filename
2filename
11filename
021filename
21filename
022filename
110filename
0111filename
0112filename
0113filename
211filename
212filename
1101filename

主要观点仍然存在:您可以定义自己的排序例程,以便按照您想要的方式进行排序。

顺便说一句,Schwartzian Transformation是一种创建排序键的方法,按这些键排序,然后剥离这些键。 可以更有效率。

想象一下像这样的排序例程:

sort { foo($a) <=> foo($b) } @array

它很好而且简短,但是对于每次比较,你必须运行foo子程序两次。如果需要一段时间来计算foo(让我们说一些货币价值计算),您的排序可能需要很长时间才能运行。

相反,您将遍历数组,并为该数组生成排序键。这种情况的常见方法是创建哈希。排序键存储在哈希中,值是您要排序的值。然后,您按键对哈希进行排序,然后进行排序。在这种情况下,foo只需要为每个排序键运行一次,而不是两次。

但是,必须小心。如果您创建重复的排序键,则可能会丢失一个您尝试在哈希中排序的值。例如,我正在查看我可以获得的各种抵押贷款,并且我希望按照我必须计算的实际APR对它们进行排序。如果这些抵押贷款中的两个或多个具有相同的年利率,我将失去一些抵押贷款。这就是为什么你必须仔细遵循维基百科页面上列出的编码模板的原因。

在这种情况下,对于每次比较,我将拆分排序键两次。它可能会更有意义做的Schwartzian转换,但如果你不&#39; t有很多文件,(说...少于100,000个文件进行排序),你可能赢得&#39;吨居然发出通知,除非您有什么区别基准程序。

在这种情况下,可能只需要确定排序转换的复杂性是否值得代码中的复杂性和(混淆)。如果这是每分钟服务数百万个请求的服务器的一部分,那么使用Schwartzian转换 2 可能是值得的。


1。不是真的。 Perl在标准字符串比较顺序中排序,这实际上取决于您当前的排序规则locale。然而,取决于unicode的,字符编码,计算机&#39; S的区域设置,和其他的东西一大堆我声称要明白,但真不&#39; T,所以我&#39; 11假装它&#39; S如果你这样做,就按ASCII顺序排序。

2 然后,它可能不会。您可以做的最糟糕的事情之一是优化代码,而不知道您需要优化的位置。 I&#39;一直在对其中的开发者花费几天和几周内优化这是很少使用的代码段,从来没有得到周围改善代码在优化可能会做最擅长的部分项目太多

答案 4 :(得分:0)

使用自定义排序来解决您的目的。以下是一个示例:http://icfun.blogspot.com/2008/05/perl-customize-sorting-with-array.html