1

これを機能させるには、ある種のシュワルツ変換が必要だと思いますが、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;
    }
}
4

6 に答える 6

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
于 2010-06-02T18:52:31.077 に答える
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モジュールも見てください。「アルバム」という単語で始まるファイルだけを選択できます。readdirよりも少し簡単だと思います。

于 2010-06-02T19:04:41.733 に答える
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 Transformは別の問題を解決します。それは、検索アルゴリズムで複雑なタスク(必要なタスクとは異なり-正規表現)を複数回実行する必要をなくします。結果をキャッシュする必要があるというメモリコストがかかります(予期しないことではありません)。基本的に、問題の入力を出力(通常は配列)にマップしてから、出力[$input, $output]を並べ替えます$a->[1] <=> $b->[1]。並べ替えが完了したら、元の入力を取得するためにマップを元に戻します$_->[0]

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

とてもコンパクトで効率的なのでかっこいいです。

于 2010-06-02T19:18:25.943 に答える
1

シュワルツ変換を使用して、次の手順を実行します。

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
于 2010-06-02T19:28:14.203 に答える
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
于 2010-06-02T19:47:26.747 に答える
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
      ;
于 2010-06-02T20:35:00.030 に答える