【问题标题】:sorting a PERL array by date on ASCII在 ASCII 上按日期对 PERL 数组进行排序
【发布时间】:2014-01-04 02:36:39
【问题描述】:

我需要做的是编写一个脚本来读取目录列表 然后对它们进行排序,最后一个目录将被“弹出”。

我的 $last_one = pop @sorted;

然后最后一个目录将被删除 - 使用 system("rm- rf $last_one) 或 删除树($last_one)。

 1  #!/usr/bin/perl
 2  use strict;
 3  use warnings;
 4
 5  my $dir_to_process = "/production/log/fo/archive/";
 6  opendir DH, $dir_to_process or die "Sorry, this is not going to work out $!";
 7
 8  while (my $name = readdir DH) {
 9       next if $name =~ /^\./;
10       push(my @unsorted,$name) ;
11       my @sorted_dir = sort @unsorted;
12       foreach my $sorted (@sorted_dir) {
13       print "$sorted\n";
14       sleep 1 ;
15       }
16
17  }

但是,我在对目录进行排序时遇到了很多麻烦 - 它们是用这种格式编写的。 这是实际的输出。

2013Nov12
2013Sep14
2013Jul15
2013Jan20
2013Sep11
2013May31
2013Jul04
2012Dec09
2013Oct12
2013Oct09
2012Dec27
2013Nov28
2013Mar24
2013Jun06
2013Jun25

【问题讨论】:

  • 您在标题中说“按 ASCII 排序”,但我认为您真正想要的是按日期排序(即从最早到最新,反之亦然)?
  • 如果你只想找到最旧的,那么排序是没有意义的。
  • 问题是它是按 ascii 排序的,而不是按日期排序 - 是的,我需要的是最旧的日期目录。

标签: arrays perl sorting


【解决方案1】:

sort() 接受代码块,您可以在其中定义自己的排序算法。只需将您的目录名称转换为时间戳即可。这就像
sort { date2stamp($a) <=> date2stamp($b) } @unsorted
在 date2stamp 子中使用 POSIX::mktime() 为日期字符串创建正确的时间戳。

【讨论】:

    【解决方案2】:

    您需要一个函数将目录名称转换为日期(将日期信息拆分为您可以排序的内容:年、月、日)。

    这是一个例子

    sub parsedate {
      my $name = shift;
      my %months = ( 'Jan'=> 1, 'Feb' => 2, 'Mar' => 3, 'Apr' => 4,
        'May'=> 5, 'Jun' => 6, 'Jul' => 7, 'Aug' => 8,
        'Sep'=> 9, 'Oct' => 10, 'Nov' => 11, 'Dec' => 12 );
    
      my ($y,$m,$d);
      $name =~ m/^(\d{4})(\w{3})(\d{2})$/ 
        and ($y,$m,$d) = ($1,$months{$2},$3)
        or die "file name $name doesn't match";
    
      return sprintf("%04d%02d%02d",$y,$m,$d);
    }
    

    现在您可以使用您的新子 parsedate 对它们进行排序。

    my @sorted_dir = sort {parsedate($a) <=> parsedate($b)} @unsorted;
    

    【讨论】:

      【解决方案3】:

      将月份名称转换为数字,然后从 y m d 中生成一个数字 然后按数字对数字进行排序 “schwartzian 变换”用于将数字附加到文件名

      #!/usr/bin/perl
      #
      #
      use warnings;
      use strict;
      
      my %monthval=qw(Jan 01 Feb 02 Mar 03 Apr 04 May 05 Jun 06 Jul 07 Aug 08 Sep 09 O
      ct 10 Nov 11 Dec 12);
      
      
      my @in=qw(2013Nov12
      2013Sep14
      2013Jul15
      2013Jan20
      2013Sep11
      2013May31
      2013Jul04
      2012Dec09
      2013Oct12
      2013Oct09
      2012Dec27
      2013Nov28
      2013Mar24
      2013Jun06
      2013Jun25);
      
      my @sorted = map{$_->[0]} sort { $a->[1] <=> $b->[1]}  map { my ($y,$m,$d)=/(\d{
      4})(\w{3})(\d{2})/; [$_,"$y".$monthval{$m}."$d"]} @in;
      
      foreach my $name (@sorted) {
        print "$name\n";
      }
      

      【讨论】:

        【解决方案4】:

        让我们假设存在一个函数convert_date,它将您的日期转换为 YYYYMMDD 格式。如果你有那个,一个简单的字符串比较会找到最旧的。

        my ($oldest) =
           sort { convert_date($a) cmp convert_date($b) }
           @dirs;
        

        更快:

        my ($oldest) =
           map $_->[0],
           sort { $a->[1] cmp $b->[1] }
           map [ $_, convert_date($_) ],
           @dirs;
        

        最快:

        my ($oldest) =
           map substr($_, 8),
           sort
           map convert_date($_) . $_,
           @dirs;
        

        但是排序 (O(N log N)) 是一种寻找一个元素 (O(N)) 的浪费方式。

        my $oldest = $dirs[0];
        for (@dirs) {
           $oldest = $_ if convert_date($_) lt $oldest;
        }
        

        更快?

        use List::Util qw( minstr );
        my $oldest = substr(minstr( map { convert_date($_) . $_ } @dirs ), 8);
        

        现在,剩下的就是写convert_date

        use Carp qw( croak );
        
        my %month_num_by_en_name = (
           Jan => 1,  Feb =>  2,  Mar =>  3,  Apr =>  4,
           May => 5,  Jun =>  6,  Jul =>  7,  Aug =>  8,
           Sep => 9,  Oct => 10,  Nov => 11,  Dec => 12,
        );
        
        
        sub convert_date {
           my ($date) = @_;
        
           my ($y,$m,$d) = $date =~ m/^(\d{4})(\w{3})(\d{2})\z/
              or croak("Invalid input");
        
           $m = $month_num_by_en_name{$m}
              or croak("Invalid input");
        
           return sprintf("%04d%02d%02d", $y,$m,$d);
        }
        

        您也可以使用DateTime::Format::Strptime。这样可以更轻松地支持其他语言。

        use DateTime::Format::Strptime qw( );
        
        my $format = DateTime::Format::Strptime->new(
           pattern  => '%Y%b%d',
           locale   => 'en_US',
           on_error => 'croak',
        );
        
        sub convert_date {
           my ($date) = @_;
           return $format->parse_datetime($date)->strftime('%Y%m%d');
        }
        

        【讨论】:

        • 很棒的解释。你介意我问你为什么使用Carp吗?是出于习惯还是有收获(总是在谈论 OP 的场景)?
        • @foibs,因为 Carp 提供了croak,这里很合适。
        • 抱歉,我不清楚。我的意思是croakdie 更合适。谢谢
        • 在库代码中通常最好使用croak/carp 而不是die/warn。因为您的用户(我的意思是使用您的库的程序员)并不关心代码中的错误在哪里。他关心他在哪里使用无效参数调用您的函数。
        • @DaveCross 谢谢。这已经不习惯了,因为这里我们讨论的是一个简单的脚本,既不是库也不是模块。这是一个我可以添加的好习惯。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-02-23
        • 2016-09-30
        • 1970-01-01
        • 2020-07-22
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多