我想使用一个使用子类比较函数的超类排序。我试图在下面的代码中提炼出问题的本质。这不是“生产”代码,但在此处提供以供说明。它经过测试。
#!/usr/bin/perl
# $Id: foo,v 1.10 2019/02/23 14:14:33 bennett Exp bennett $
use strict;
use warnings;
package Fruit;
use Scalar::Util 'blessed';
sub new {
my $class = shift;
my $self = bless({}, $class);
$self->{itemList} = [];
warn "Called with class ", blessed $self, "\n";
return $self;
}
package Apples;
use parent qw(-norequire Fruit);
sub mySort {
my $self = shift;
@{$self->{itemList}} = sort compare @{$self->{itemList}};
return $self;
}
sub compare {
$a->{mass} <=> $b->{mass};
}
package main;
my $apfel = Apples->new();
push(@{$apfel->{itemList}}, { "name" => "grannysmith", "mass" => 12 });
push(@{$apfel->{itemList}}, { "name" => "macintosh", "mass" => 6 });
push(@{$apfel->{itemList}}, { "name" => "Alkmene", "mass" => 8 });
$apfel->mySort();
for my $f (@{$apfel->{itemList}}) {
printf("%s is %d\n", $f->{name}, $f->{mass});
}
exit 0;
我想做的是将mySort()
移动到抽象超类Fruit
。我尝试了很多方法来解决$self->compare()
子程序,但我没有太多运气。
有什么想法吗?
我已经得到它来调用正确的子程序,但从来没有使用正确的$a
和$b
。我已经把所有失败的尝试都排除在这个问题之外,希望有人能够立即知道如何将mySort()
移动到Fruit
包中,这样我就能用同样的子程序对橘子进行分类。
你有两个问题。首先,您需要超类中的mySort
函数来为正确的子类调用compare
函数。其次,您需要子类中的compare
函数才能从另一个包中的调用接收它想要比较的两个元素。
目前尚不清楚你是否找到了第一个问题的解决方案,但一个解决方案是使用UNIVERSAL::can
找出正确的比较方法。
package Fruit;
sub mySort {
my $self = shift;
my $compare_func = $self->can("compare");
@{$self->{itemList}} = sort $compare_func @{$self->{itemList}};
}
这将找到正确的子类compare
函数并在sort调用中使用它。
现在Apples::compare
函数中的问题是,当Fruit::mySort
准备比较几个元素时,它将设置包变量$Fruit::a
和$Fruit::b
,而不是$Apples::a
和$Apples::b
。所以你的Apples::compare
函数必须为此做好准备。以下是几个解决方案:
package Apples;
sub compare {
package Fruit;
$a->{mass} <=> $b->{mass};
}
要么
sub compare {
$Fruit::a->{mass} <=> $Fruit::b->{mass}
}
或更多的防守,
package Apples;
sub compare {
my $pkg = caller;
if ($pkg ne __PACKAGE__) {
no strict 'refs';
$a = ${"${pkg}::a"};
$b = ${"${pkg}::b"};
}
$a->{mass} <=> $b->{mass}
}
更新:我考虑制作一个子程序属性,将$a
和$b
值复制到正确的包中,但在对其进行基准测试并考虑替代方案后,我决定反对它。以下是后人的结果:
考虑三个排序例程(可能在另一个包中并且很难从当前包中使用)
sub numsort { $a <=> $b }
sub lexsort { $a cmp $b }
sub objsort { $a->{value} <=> $b->{value} }
以下是我们可以使这些包可访问的一些方法:
$a
和$b
变量。实现太长,不能包括在这里,但子声明看起来像
sub numsort : CrossPkg { $a <=> $b }
$_[0]
和$_[1]
而不是$a
和$b
,并在sort
调用中使用包装器
sub lexcmp { $_[0] cmp $_[1] }
...
@output = sort { lexcmp($a,$b) } @input
$a
和$b
值。
@output = do { package OtherPackage; sort numsort @input };
以下是基准测试结果。 local
方法是普通的sort
调用,没有跨包问题。
Rate attrib-numsort wrap-numcmp local-numsort repkg-numsort attrib-numsort 1.17/s -- -90% -96% -96% wrap-numcmp 11.6/s 885% -- -61% -64% local-numsort 29.5/s 2412% 155% -- -8% repkg-numsort 32.2/s 2639% 178% 9% -- Rate attrib-lexsort repkg-lexsort wrap-lexcmp local-lexsort attrib-lexsort 3.17/s -- -12% -14% -17% repkg-lexsort 3.60/s 13% -- -2% -5% wrap-lexcmp 3.68/s 16% 2% -- -3% local-lexsort 3.80/s 20% 6% 3% -- Rate attrib-objsort wrap-objcmp local-objsort repkg-objsort attrib-objsort 1.22/s -- -81% -88% -89% wrap-objcmp 6.32/s 417% -- -38% -44% local-objsort 10.1/s 730% 61% -- -10% repkg-objsort 11.3/s 824% 79% 11% --
总结:lexsort
不需要考虑开销,每次比较需要更多时间。属性方法在到达时已经死亡。设置进入sort
调用的包具有最好的结果 - 或多或少没有开销 - 但它不适合这个应用程序(在对象层次结构中)。重写比较函数并在sort
调用中包装函数对于性能下降来说并不算太差,并且它在对象层次结构中工作,因此最终的建议是:
package Fruit;
sub compare { ... }
sub mySort {
my $self = shift;
@{$self->{itemList}} =
sort { $self->can("compare")->($a,$b) } @{$self->{itemList}};
}
package Apples;
our @ISA = qw(Fruit)
sub compare { $_[0]->{mass} <=> $_[1]->{mass} }
标点变量如$_
[1]被称为“超全局”,因为它们引用了main::
命名空间中的变量。[2]换句话说,无论目前的包装是什么,$_
都是$main::_
的缩写。
$a
和$b
不是超级全局的。它们是普通的包变量。 sort
填充了发现$a
的包装的$b
和sort
,如果在不同的包装中找到sort
和比较功能,则会导致问题。这意味着将mySort
移动到Fruit ::将导致sort
填充$Fruit::a
和$Fruit::b
,但是你的compare
函数读取$Apple::a
和$Apple::b
。
当涉及多个包时,您可以使用一些解决方案,但最简单的方法是在比较函数上使用($$)
原型。这导致sort
将值作为参数传递而不是使用$a
和$b
。
package Foo;
my $compare = \&Bar::compare;
my @sorted = sort $compare @unsorted;
package Bar;
sub compare($$) { $_[0] cmp $_[1] }
sort
将sub称为函数,而不是方法。如果你想将它作为方法调用,你需要一个包装器。
package Foo;
my @sorted = sort { Bar->compare($a, $b) } @unsorted;
package Bar;
sub compare { $_[1] cmp $_[2] }
也就是说,在一个类中使用sort
并在子类中使用分类器的想法存在根本缺陷。你可能有一个包含苹果和橙子的列表,那么如何确定调用哪种compare
方法?
package Foo;
my @sorted = sort { ???->compare($a, $b) } @unsorted;
package Bar;
sub compare { $_[1] cmp $_[2] }
STDIN
这样的名字。$package::_
),您可以访问其他包的标点变量。这些没有特别的意义;它们不被Perl本身使用。变量$a
和$b
被sort
用作调用sort的同一个包中的包变量,因此为了让子类看到它们,你可以试试这个。
在父类中:
sub mySort {
my $self = shift;
@{$self->{itemList}} = sort { $self->compare($a, $b) } @{$self->{itemList}};
return $self;
}
在子类中:
sub compare {
my ( $self, $a, $b ) = @_;
$a->{mass} <=> $b->{mass};
}