为什么这些不同的Perl排序子程序给我不同的元素顺序?

时间:2009-11-18 08:27:46

标签: perl sorting

我从perldoc -f sort复制了这个例子。我添加了@old数组和@new数组的打印件。为什么我会得到三个不同的@new数组?我的@old是否有问题?

@old = qw( =332 =43 =avxc =aed =jjj =3322 =aa44 =ssss );
say "\nold  :   @old\n";

# inefficiently sort by descending numeric compare using
# the first integer after the first = sign, or the
# whole record case-insensitively otherwise
@new = sort {
($b =~ /=(\d+)/)[0] <=> ($a =~ /=(\d+)/)[0]
||
uc($a) cmp uc($b)
} @old;
say "new_1:   @new"; # =3322 =332 =43 =aa44 =aed =avxc =jjj =ssss

# same thing, but much more efficiently;
# we'll build auxiliary indices instead
# for speed
@nums = @caps = ();
for (@old) {
push @nums, /=(\d+)/;
push @caps, uc($_);
}
@new = @old[ sort {
$nums[$b] <=> $nums[$a]
||
$caps[$a] cmp $caps[$b]
} 0..$#old
];
say "new_2:   @new"; # =avxc =332 =43 =3322 =aa44 =aed =jjj =ssss

# same thing, but without any temps
@new = map { $_->[0] }
sort { $b->[1] <=> $a->[1]
||
$a->[2] cmp $b->[2]
} map { [$_, /=(\d+)/, uc($_)] } @old;
say "new_3:   @new\n"; # =3322 =332 =43 =avxc =aed =jjj =aa44 =ssss

2 个答案:

答案 0 :(得分:4)

清空列表

三个列表不同,因为正则表达式

/=(\d+)/

可能会返回一个空列表(当它不匹配时)---搞乱你的结构。我在代码中插入了以下行并得到了指示的答案:

say "nums: @nums";  # nums: 332 43 3322
say "caps: @caps";  # caps: =332 =43 =AVXC =AED =JJJ =3322 =AA44 =SSSS

你知道,@ nums中缺少任何与正则表达式不匹配的项目并且会弄乱你的列表。 这可以通过将@nums的定义更改为:

来解决
push @nums, /=(\d+)/? $1:undef;

修改:添加了说明)上一个示例中出现了同样的问题:

For $_="=123", [$_, /=(\d+)/, uc($_)] = ["123",  123,  123].
For $_="abcd", [$_, /=(\d+)/, uc($_)] = ["abcd", "ABCD"].

匹配消失,大写字符串移动到位。这不是你想要的。一个解决方法是,如上所述,用一个总是只生成一个标量的表达式替换正则表达式:

[$_, /=(\d+)/? $1:undef, uc($_)]

另一个解决方案是交换列表中的最后两个元素:

@new = map { $_->[0] }                                                          
sort { $b->[2] <=> $a->[2]                                                      
||                                                                              
$a->[1] cmp $b->[1]                                                             
} map { [$_, uc($_), /=(\d+)/] } @old;                                          
say "new_3:   @new\n";

现在,正则表达式结束了。如果它不产生匹配,则列表很短(只有两个元素)。然而,$ a-> [2]产生了期望的结果:undef。 (你可能喜欢也可能不喜欢这种让bug咬人的方法,但是得到正确的结果是perl读取的副作用超出了较短列表的末尾。)

通过这些修复,所有三个列表都会产生相同的结果。

警告

请在启用“-w”警告的情况下运行程序。你发现你比较了很多未定义的值和非数值。如果没有这个,你应该修改你的程序,例如:

@new = map { $_->[0] }                                                          
sort {                                                                          
defined($b->[2]) && defined($a->[2]) && ($b->[2] <=> $a->[2])                   
||                                                                              
$a->[1] cmp $b->[1]                                                             
} map { [$_, uc($_), /=(\d+)/] } @old;                                          
say "new_4:   @new\n";

答案 1 :(得分:1)

这是使用用户定义的比较器对数组进行排序的三个示例。每个示例在运行后输出已排序的数组。

由于有三个示例,排序的数组会打印三次。当然,当您要排序时,不应该使用所有三个版本。

最后一个示例使用Schwartzian Transform

我认为原作者的意图是所有示例都生成相同的排序输出,但由于Yaakov Belch解释的错误,它们没有。

请注意,您的数据与您使用的比较器的预期不符。您正在使用的比较器确实希望所有记录的格式为=\d.+(即等号,后跟数字后跟任意字符)。如果所有记录都不符合这种格式,则需要在比较器中小心。

修正了下面的第一个和第三个例子。我认为没有必要使用示例2:Either it pays to pre-compute or it doesn't。如果是,请使用Schwartzian变换。如果没有,请使用为每次比较提取密钥的常规排序。

#!/usr/bin/perl

use 5.010;
use strict; use warnings;

my @old = qw( =332 =43 =avxc =aed =jjj =3322 =aa44 =ssss );

my @new_1 = sort {
    my $ad = $a =~ /=(\d+)/ ? $1 : undef;
    my $bd = $b =~ /=(\d+)/ ? $1 : undef;
    return  1 if defined $bd and not defined $ad;
    return -1 if not defined $bd and defined $ad;
    return $bd <=> $ad if defined $ad and defined $bd;
    return uc $a cmp uc $b;
} @old;

say "new_1:\t@new_1\n"; # =3322 =332 =43 =avxc =aed =jjj =aa44 =ssss

my @new_3 = map { $_->[0] }
    sort {
        return  1 if defined $b->[1] and not defined $a->[1];
        return -1 if not defined $b->[1] and defined $a->[1];
        return  $b->[1] <=> $a->[1] if defined $b->[1] and defined $a->[1];
        return  $a->[2] cmp $b->[2];
} map { [$_, /=(\d+)/ ? $1 : undef, uc($_)] } @old;

say "new_3:\t@new_3\n"; # =3322 =332 =43 =avxc =aed =jjj =aa44 =ssss

输出:

new_1:  =3322 =332 =43 =aa44 =aed =avxc =jjj =ssss

new_3:  =3322 =332 =43 =aa44 =aed =avxc =jjj =ssss