【问题标题】:Can I call a superclass sort with a subclass compare in perl?我可以用perl中的子类比较来调用超类排序吗?
【发布时间】:2019-02-23 14:25:16
【问题描述】:

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

#!/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-&gt;compare() 子例程的方法,但运气不佳。

有什么想法吗?

我已经让它调用正确的子例程,但从来没有使用正确的$a$b。我已将所有失败的尝试都排除在这个问题之外,希望有人会立即知道如何将 mySort() 移动到 Fruit 包,以便我可以使用相同的子程序对橘子进行分类。

【问题讨论】:

  • 嗯,我当然有一些事情要检查下面的答案。我会在一段时间内回复大家。到目前为止,所有的答案似乎都是一致的。 @ikegami 建议我可能会尝试将苹果与橙子进行比较。永远不会是这样。我只想在不同的子类上调用相同的mySort(),每个子类都有自己的compare()。到目前为止,答案看起来很有希望。

标签: perl subclass superclass subroutine


【解决方案1】:

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

$a$b 不是超级全局变量。它们是普通的包变量。 sort 填充找到sort 的包的$a$b,如果在不同的包中找到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 放在一个类中而将排序器放在一个子类中的想法从根本上是有缺陷的。您可能有一个包含 Apples 和 Oranges 的列表,那么您如何确定要调用哪个 compare 方法?

package Foo;
my @sorted = sort { ???->compare($a, $b) } @unsorted;

package Bar;
sub compare { $_[1] cmp $_[2] }

  1. 还有一些命名的,例如STDIN

  2. 通过使用完全限定名称(例如$package::_),您可以访问其他包的标点符号变量。这些没有特殊含义; Perl 本身不使用它们。

【讨论】:

  • 与你一生都被告知的相反,you can compare Apples and Oranges
  • @mob,抛开诙谐,这有点说明我的观点。您可以通过透射光谱、质量等比较水果,但不能使用苹果特有的比较功能来比较水果。
【解决方案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 函数并在排序调用中使用它。

现在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. 在正确的包中执行排序调用,从而设置正确的$a$b 值。

    @output = do { package OtherPackage; sort numsort @input };
    

以下是基准测试结果。 local 方法是普通的sort 调用,没有跨包问题。

评价 attrib-numsort wrap-numcmp local-numsort repkg-numsort 属性编号排序 1.17/s -- -90% -96% -96% 包裹 numcmp 11.6/s 885% -- -61% -64% 本地数字排序 29.5/s 2412% 155% -- -8% repkg-numsort 32.2/s 2639% 178% 9% -- 评价 attrib-lexsort repkg-lexsort wrap-lexcmp local-lexsort 属性 lexsort 3.17/s -- -12% -14% -17% repkg-lexsort 3.60/s 13% -- -2% -5% 换行 lexcmp 3.68/s 16% 2% -- -3% 本地 lexsort 3.80/s 20% 6% 3% -- 评价 attrib-objsort wrap-objcmp local-objsort repkg-objsort 属性-objsort 1.22/s -- -81% -88% -89% 包裹-objcmp 6.32/s 417% -- -38% -44% 本地对象排序 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} }

【讨论】:

  • 规则! warn()ings 正是您所期望的。我会发布文本,但我不知道如何在 cmets 中进行预格式化。它显示new() called with class ApplesmySort() called with class Applescompare() called with class Fruit。我使用了“更具防御性”的compareUNIVERSAL-&gt;can()
【解决方案3】:

变量$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};
}

【讨论】:

    猜你喜欢
    • 2013-11-06
    • 1970-01-01
    • 2014-09-24
    • 1970-01-01
    • 2016-06-17
    • 2010-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多