【问题标题】:How can I sort an array so that certain file extensions sort to the top?如何对数组进行排序,以便某些文件扩展名排序到顶部?
【发布时间】:2010-03-26 17:36:30
【问题描述】:

我有一个包含文件列表的数组。我想对它进行排序,让我在数组的开头有 .txt 文件,然后是其余的文件。

这就是我现在正在做的,效果很好。

@files = (grep(/\.txt$/,@files),grep(!/\.txt$/,@files));

有没有更好的方法呢?

【问题讨论】:

  • 这是 O(n),而下面的排序是 O(n log n)。如果你想要一个分区而不是一个排序,那么它是一个更好的选择。但是,如果您想排序,请在一次操作中完成所有操作。

标签: perl arrays


【解决方案1】:

您询问了有关对多个文件扩展名执行此操作的后续评论。在这种情况下,我会建立 Schwartzian 变换。如果您是 ST 的新手,我推荐 Joseph Hall 在Effective Perl Programming 中的解释。虽然Second Edition 很快就会出现,但我们基本上保持原样,所以first edition 也一样好。 Google 图书似乎只显示第一版的每页一英寸,所以你不走运。

在这个答案中,我使用加权函数来决定哪些扩展应该移到顶部。如果扩展没有明确的权重,我只是按字典顺序对其进行排序。您可以随意使用sort 来获得您想要的确切订单:

@files = qw(
    buster.pdf
    mimi.xls
    roscoe.doc
    buster.txt
    mimi.txt
    roscoe.txt
    buster.rpm
    mimi.rpm
    );

my %weights = qw(
    txt 10
    rpm  9
    );

my @sorted = 
    map { $_->{name} }
    sort { 
        $b->{weight} <=> $a->{weight}
         ||
        $a->{ext}    cmp $b->{ext}
         ||
        $a cmp $b
        }
    map {
        my( $ext ) = /\.([^.]+)\z/;
            { # anonymous hash constructor
            name => $_,
            ext => $ext,
            weight => $weights{$ext} || 0,
            }
        }
    @files;

$" = "\n";
print "@sorted\n";

【讨论】:

    【解决方案2】:

     

    @sorted = sort { $b=~/\.txt$/ <=> $a=~/\.txt$/  ||  $a cmp $b } @files
    

    将 .txt 文件放在首位,否则按字典顺序(按字母顺序)排序。

    @sorted = sort { $b=~/\.txt$/ <=> $a=~/\.txt$/ } @files
    

    将 .txt 文件放在首位,否则保留原始顺序(sort 自 Perl 5.8 以来稳定

    【讨论】:

      【解决方案3】:

      您只需在每个greps 前面添加一个sort

       my @sorted =
         (
         sort( grep /\.txt\z/,   @files ),
         sort( grep ! /\.txt\z/, @files )
         );
      

      这里的诀窍是您对列表进行分区,然后对每个分区进行独立排序。根据您正在做的事情,这可能比尝试在一种操作中完成所有事情要好得多。相反,它可能并不总是更好。

      还有多种其他方法可以完成此任务,但并非如此简单。 :)

      这是我的 MacBook Air 上的一个快速基准测试,带有 vanilla Perl 5.10.1:

      There are 600 files to sort
           brian:  3 wallclock secs @ 369.75/s (n=1161)
         control:  3 wallclock secs @ 1811.99/s (n=5744)
            leon:  4 wallclock secs @ 146.98/s (n=463)
         mobrule:  3 wallclock secs @ 101.57/s (n=324)
            sort:  4 wallclock secs @ 559.62/s (n=1746)
      

      这是脚本:

      use Benchmark;
      
      use vars qw(@files);
      
      @files = qw(
          buster.pdf
          mimi.xls
          roscoe.doc
          buster.txt
          mimi.txt
          roscoe.txt
          ) x 100;
      
      
      printf "There are %d files to sort\n", scalar @files;
      
      sub leon {  
          my @sorted = 
              map { $_->[0] } 
              sort { $a->[1] <=> $b->[1] } 
              map { [ $_, !/\.txt$/ ] 
              } @files;
          }
      
      sub brian {
           my @sorted =
             (
             sort( grep /\.txt\z/,   @files ),
             sort( grep ! /\.txt\z/, @files )
             );
          }
      
      sub mobrule {
          my @sorted = 
              sort { ($b=~/\.txt\z/) <=> ($a=~/\.txt\z/)  ||  $a cmp $b } 
              @files;
          }
      
      sub plain_sort {
          my @sorted = sort @files;
          }
      
      sub control {
          my @sorted = @files;
          }
      
      timethese( -3,
           {
           brian   => \&brian,
           leon    => \&leon,
           mobrule => \&mobrule,
           control => \&control,
           sort    => \&plain_sort,
           }
           );
      

      【讨论】:

      • 在所有这些中,我认为这可能是最干净和最明显的。
      • 根据他关于想要更多文件扩展名的后续评论,这可能不是正确的答案。
      【解决方案4】:

      Sort 将可选块作为第一个参数,但在这种情况下,Schwartzian 变换会更快。

      @files = map { $_->[0] } sort { $a->[1] <=> $b->[1] } map { [ $_, !/\.txt$/ ] } @files;
      

      【讨论】:

      • 在我的测试中,我发现 Schwartzian 变换有点慢(但只有一点点)。在我的回答中,我必须对数组进行两次传递,但在您的示例中,您也是如此。不过,您也必须提供参考。
      • 我第一次听说 Schwartzian 变换。这绝对很有趣。假设我首先想要 txt 文件,然后是 rpm,然后是其余文件。上面的代码需要如何更改?我不确定我到底了解它在做什么。
      【解决方案5】:

      为了有效地处理多个扩展,您可以修改 brian d foy 的排序 greps,方法是一次性对数组进行分区,然后对每个分区进行独立排序。

      use strict;
      use warnings;
      
      use List::MoreUtils qw(part);
      
      my @files = qw(
          bar        Bar.pm       bar.txt
          bar.jpeg   foo          foo.pm
          foo.jpeg   zebra.txt    zebra.pm
          foo.bat    foo.c        foo.pl
          Foo.pm     foo.png      foo.tt
          orange     apple        zebra.stripe
      );
      
      
      my @parts = part { get_extension_priority($_) } @files;
      
      my @sorted = map { sort( @{ $_ || [] } ) } @parts; 
      
      print map "$_\n", @sorted;
      
      BEGIN {
      
          # Set extension priority order
          my @priority = qw( stripe txt nomatch pl jpeg  );
      
          # make a hash to look up priority by extension
          my %p = map { $priority[$_], $_ } 0..$#priority;
      
          sub get_extension_priority {
              my $file = shift;
      
              return scalar @priority 
                  unless /[.](\w*)$/;
      
              return scalar @priority 
                  unless exists $p{$1};
      
              return $p{$1};
          }
      }
      

      【讨论】:

      • 非常好!不过,每次我看到 part() 时,我都希望他刚刚将其命名为 partition() :)
      【解决方案6】:

      打高尔夫球? 这不会产生讨厌的警告:

      @files = map { $_->[0] } sort { @$b <=> @$a } map { [$_, /\.txt$/] } @files
      

      【讨论】:

      • 不,我不是在玩 Code Golf。我正在使用 Net::FTPSSL 编写一个 FTP 客户端,遇到了一种情况,我需要按特定顺序下载文件,我想知道是否有比我现在做的更好的排序方式。感谢您的回答。
      • 我几乎可以肯定@$b 应该是错误的,但我不能让这个例子不起作用。在我看来,@$b 应该被强制转换为一个数字,而不是比较数组中的某些内容,但我想这不会发生。为什么会起作用?
      • @brian d foy - 如果 /\.txt$/ 匹配,它给出 1 并且结果对数组的引用将包含类似 ['foo.txt', 1];如果没有,/\.txt$/ 会生成空列表,该列表又会引用 ['foo.bin'] 之类的数组。如您所见,具有匹配结果的数组将包含 2 个元素,否则 - 1 个元素。而且,是的,你是对的,在 sort 的块中,它被强制转换为数组中的许多元素。
      • 啊,棘手。非常好。但是,我不会将其列为好风格。 :)
      • @brian d foy - 是的,我也不会把它投入生产
      猜你喜欢
      • 2010-10-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多