代码之家  ›  专栏  ›  技术社区  ›  AvatarKava

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

  •  1
  • AvatarKava  · 技术社区  · 16 年前

    我想我需要一些 Schwartzian Transform

    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 回复  |  直到 16 年前
        1
  •  7
  •   Matteo Riva    16 年前

    如果你只需要找到数字最高的专辑,你其实不需要对列表进行排序,只需要遍历它并跟踪最大数字。

    #!/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
    
        2
  •  3
  •   Robert Wohlfarth    16 年前

    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 模块。它可以让你挑选出以“相册”开头的文件。我觉得这比 读目录

        3
  •  2
  •   Evan Carroll    16 年前

    你遇到困难的原因是接线员, <=> 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变换 解决了另一个问题:它使您不必在搜索算法中多次运行复杂的任务(与您需要的任务不同——regex)。它的代价是必须缓存结果(这并不意外)。本质上,您要做的是将问题的输入映射到输出(通常在数组中) [$input, $output] 然后对输出进行排序 $a->[1] <=> $b->[1] $_->[0] .

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

    它很酷,因为它既紧凑又高效。

        4
  •  1
  •   Pedro Silva    16 年前

    给你,使用施瓦茨变换:

    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
    
        5
  •  1
  •   Ether    16 年前

    这里有一个使用 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
    
        6
  •  1
  •   Axeman maxelost    16 年前

    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
          ;