如何使用两个子字符串相等条件对字符串进行排序?

How to sort strings using two substring equality conditions?

我有一个字符串列表,格式如下:

('group1-1', 'group1-2','group1-9', 'group2-1','group2-2', 'group2-9','group1-10', 'group2-10' )

我需要按如下方式排序:

先分组,再分组。

('group1-1', 'group1-2','group1-9','group1-10', 'group2-1','group2-2', 'group2-9', 'group2-10' )

我编写了以下代码,但没有按预期运行: 一个根据组排序的比较器,如果组匹配,它根据数字排序。

my @list = ('group1-1', 'group1-2','group1-9', 
'group2-1','group2-2', 'group2-9','group1-10', 'group2-10' );
@list = sort compare @list;
for (@list){
    print($_."\n");
}

sub compare{
    my $first_group, $first_num = get_details($a);
    my $second_group, $second_num = get_details($b);
    if($first_group < $second_group){
      return -1;
   } elsif($first_group == $second_group){
      if ( $first_num < $second_num) {
         return -1;
      } elsif ( $first_num == $second_num ) {
         return 0;
      } else {
         return 1;
      }
   } else{
      return 1;                       
   }
}

sub get_details($){
   my $str= shift;
   my $group = (split /-/, $str)[0];
   $group =~ s/\D//g;
   my $num = (split /-/, $str)[1];
   $num =~ s/\D//g;
   return $group, $num;
}

我会确保列表中的字符串遵循模式 (\S+\d+-\d+),然后使用 cmp 作为字符串比较部分,使用 <=> 作为数字:

sub compare {
    if( $a =~ /(\S+)(\d+)-(\d+)/ ) {
        my($A1,$A2,$A3) = (,,);
        if( $b =~ /(\S+)(\d+)-(\d+)/ ) {
            my($B1,$B2,$B3) = (,,);
            return ($A1 cmp $B1) || ($A2 <=> $B2) || $A3 <=> $B3;
        }
    }
    $a cmp $b; # fallback if a string doesn't follow the pattern
};

您可以使用 Schwartzian transform:

use warnings;
use strict;

my @list = ('group1-1', 'group1-2','group1-9', 
    'group2-1','group2-2', 'group2-9','group1-10', 'group2-10' );

@list = map  { $_->[0] }
        sort { $a->[1] cmp $b->[1] or $a->[2] <=> $b->[2] }
        map  { [$_, split /-/] }
        @list;

for (@list) {
    print($_."\n");
}

打印:

group1-1
group1-2
group1-9
group1-10
group2-1
group2-2
group2-9
group2-10

这里的数据有一些细节可能会导致一个安静的错误。当您使用连字符前的子字符串进行排序时(group1 等),它有两个字母 数字,因此当按字典顺序排序时,多位数可能是错误的。例如

group1, group2, group10

sort-ed(默认情况下cmp)为

group1
group10
group2

我猜怎么了。

所以在排序中我们需要将groupN分解为groupN,并按N.

进行数字排序
use warnings;
use strict;
use feature 'say';

my @list = ('group1-1', 'group1-2','group1-9',
    'group2-1','group2-2', 'group2-9',
    'group1-10', 'group2-10',
    'group10-2', 'group10-1'                    # Added some 'group10' data
);


# Break input string into:  group N - N   (and sort by first then second number)

@list = 
    map  { $_->[0] }
    sort { $a->[2] <=> $b->[2] or $a->[4] <=> $b->[4] }
    map  { [ $_, /[0-9]+|[a-zA-Z]+|\-/g ] } 
    @list;

say for @list;

正则表达式从字符串中提取数字和单词,用于排序。但是如果那个单独的子字符串总是确实相同 (group) 那么我们只能按数字排序并且可以使用 /[0-9]+/g,并比较索引 1 和 [=21= 处的数组引用元素的数值].

版画

group1-1
group1-2
group1-9
group1-10
group2-1
group2-2
group2-9
group2-10
group10-1
group10-2

自然排序

你想要的是所谓的“自然排序”。

use Sort::Key::Natural qw( natsort );

my @sorted = natsort @unsorted;

也可以就地执行。

use Sort::Key::Natural qw( natsort_inplace );

natsort_inplace @array;

键排序

当您需要更多控制时。

use Sort::Key::Multi qw( uukeysort );

my @sorted = uukeysort { /(\d+)/g } @unsorted;

use Sort::Key::Multi qw( uukeysort_inplace );

uukeysort_inplace { /(\d+)/g } @array;

没有模块(未优化)

my @sorted =
   sort {
      my ($ag, $an) = $a =~ /(\d+)/g;
      my ($bg, $bn) = $b =~ /(\d+)/g;
      $ag <=> $bg || $an <=> $bn
   }
      @unsorted;

没有模块(施瓦兹变换)

这样可以避免重复同样的工作。它没有提取信息 2*N*log2(N) 次,而是只提取了 N 次。

my @sorted =
   map $_->[0],
      sort { $a->[1] <=> $b->[1] || $a->[2] <=> $b->[2] }
         map [ $_, /(\d+)/g ],
            @unsorted;

没有模块 (GRT)

ST的优化

my @sorted =
   map substr($_->[0], 8),
      sort
         map pack('NNa*', /(\d+)/g, $_),
            @unsorted;