我从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
答案 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