Как я могу отсортировать список строк по номерам в них? - PullRequest
14 голосов
/ 08 июня 2011

Легкий, без сомнения, для вас ...

У меня есть список имен файлов, которые похожи на такие;

fw_d.log.1.gz  
through  
fw_d.log.300.gz  

Когда я использую этот блок кода ниже, он почти сортируетЭто так, как я хочу, но не совсем.

#!/usr/bin/perl -w
my $basedir = "/var/log";
my @verdir = qw(fw_d);
my $fulldir;
my $configs;
my $combidir;

foreach $combidir (@verdir) {
    $fulldir = "$basedir/$combidir";
    opendir (DIR, $fulldir);
    my @files = grep { $_ ne '.' && $_ ne '..' && $_ ne 'CVS' readdir DIR;
    closedir (DIR);
    @files1 = sort {$a cmp $b}(@files);
    foreach my $configs (@files1) {
        print "Checking $configs\n";
        system("less $basedir/$combidir/$configs | grep \'.* Group = , Username = .* autheauthenticated.\' >> output.log" );
    }
}

Вот вывод фрагмента

Checking fw_d.log  
Checking fw_d.log.1.gz  
Checking fw_d.log.10.gz  
Checking fw_d.log.100.gz  
Checking fw_d.log.101.gz  
Checking fw_d.log.102.gz  

Как видите, он почти сортирует его так, как я надеялся ....У кого-нибудь есть предложения по чтению или фрагменту кода, которые я могу использовать?

Заранее спасибо.
Стив.

Ответы [ 4 ]

21 голосов
/ 08 июня 2011

Вы можете использовать Schartzian-transform :

my @sorted = map  { $_->[0] }
             sort { $a->[1] <=> $b->[1] }
             map  { [$_, $_=~/(\d+)/] }
                 @files;
print Dumper \@sorted;

Добавлен тест для сравнения между Schwartzian-Transform и подпрограммой

use Benchmark qw(:all);

# build list of files
my @files = map {'fw_d.log.'.int(rand()*1000).'.log' } 0 ..300;

my $count = -3;
my $r = cmpthese($count, {
        'subname' => sub {
              sub expand {
                   my $file=shift; 
                   $file=~s{(\d+)}{sprintf "%04d", $1}eg;
                   return $file;
              }
              my @sorted = sort { expand($a) cmp expand($b) } @files;
        },
        'schwartzian' => sub {
              my @sorted = map  { $_->[0] }
                           sort { $a->[1] <=> $b->[1] }
                           map  { [$_, $_=~/(\d+)/] }
                 @files;
         }
});

Результат:

              Rate     subname schwartzian
subname     21.2/s          --        -92%
schwartzian  279/s       1215%          --

Шварцево-преобразование примерно в 13 раз эффективнее для сортировки 300 файлов.

10 голосов
/ 08 июня 2011

проблема в том, что код выполняет то, что вам говорят: сортировать имена файлов в алфавитном порядке.

Вы должны заменить sort { $a cmp $b } на sort { expand($a) cmp expand($b) }

с expand:

sub expand 
   { my $file=shift; 
     $file=~s{(\d+)}{sprintf "%04d", $1}eg; # expand all numbers to 4 digits
     return $file;
   }
6 голосов
/ 08 июня 2011

Вы можете попробовать использовать пользовательскую функцию сортировки:

sub sort_by_number {
    $a =~ /(\d+)/;
    $numa = $1;
    $b =~ /(\d+)/;
    $numb = $1;

    return $numa <=> $numb;
}

, а затем сортировать так:

@files1 = sort sort_by_number @files;

Это позволит отсортировать строки в @files по значениюпервого числа в каждой строке.

1 голос
/ 16 мая 2017

Более старый вопрос, но есть еще не упомянутый ответ.

Sort::Naturally делает это для вас:

Сортировать лексически, но численно сортировать числовые части

#!/usr/bin/env perl
use strict;
use warnings;

use Sort::Naturally;

print nsort <DATA>;

__DATA__
fw_d.log  
fw_d.log.101.gz  
fw_d.log.1.gz  
fw_d.log.10.gz  
fw_d.log.100.gz 
fw_d.log.2.gz  
fw_d.log.102.gz  
fw_d.log.12.gz

Этот заказ как:

fw_d.log  
fw_d.log.1.gz  
fw_d.log.2.gz  
fw_d.log.10.gz  
fw_d.log.12.gz
fw_d.log.100.gz 
fw_d.log.101.gz  
fw_d.log.102.gz  
...