我可以在perl中使用子类比较调用超类排序吗?

问题描述 投票:1回答:3

我想使用一个使用子类比较函数的超类排序。我试图在下面的代码中提炼出问题的本质。这不是“生产”代码,但在此处提供以供说明。它经过测试。

#!/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包中,这样我就能用同样的子程序对橘子进行分类。

perl subclass superclass subroutine
3个回答
2
投票

你有两个问题。首先,您需要超类中的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} }

以下是我们可以使这些包可访问的一些方法:

  1. 实现一个子程序属性,在正确的包中准备$a$b变量。实现太长,不能包括在这里,但子声明看起来像 sub numsort : CrossPkg { $a <=> $b }
  2. 重写比较函数来比较$_[0]$_[1]而不是$a$b,并在sort调用中使用包装器 sub lexcmp { $_[0] cmp $_[1] } ... @output = sort { lexcmp($a,$b) } @input
  3. 在正确的包中执行sort调用,因此它设置正确的$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} }

4
投票

标点变量如$_ [1]被称为“超全局”,因为它们引用了main::命名空间中的变量。[2]换句话说,无论目前的包装是什么,$_都是$main::_的缩写。

$a$b不是超级全局的。它们是普通的包变量。 sort填充了发现$a的包装的$bsort,如果在不同的包装中找到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] }

  1. 还有一些像STDIN这样的名字。
  2. 通过使用完全限定名称(例如$package::_),您可以访问其他包的标点变量。这些没有特别的意义;它们不被Perl本身使用。

1
投票

变量$a$bsort用作调用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};
}
© www.soinside.com 2019 - 2024. All rights reserved.