我可以在 perl 中调用带有子类比较的超类排序吗? [英] Can I call a superclass sort with a subclass compare in perl?

查看:46
本文介绍了我可以在 perl 中调用带有子类比较的超类排序吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想使用使用子类比较函数的超类排序.我试图在以下代码中提炼问题的性质.这不是生产"代码,而是在此处提供以供说明.已经测试过了.

#!/usr/bin/perl# $Id: foo,v 1.10 2019/02/23 14:14:33 bennett Exp bennett $使用严格;使用警告;包装水果;使用 Scalar::Util 'blessed';子新{我的 $class = shift;我的 $self = bless({}, $class);$self->{itemList} = [];警告与类一起调用",祝福 $self,\n";返回 $self;}包装苹果;使用父 qw(-norequire Fruit);子排序{我的 $self = shift;@{$self->{itemList}} = 排序比较@{$self->{itemList}};返回 $self;}子比较{$a->{mass} <=>$b->{质量};}包主;我的 $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();对于我的 $f (@{$apfel->{itemList}}) {printf("%s 是 %d\n", $f->{name}, $f->{mass});}退出 0;

我想要做的是将 mySort() 移动到抽象超类 Fruit.我已经尝试了多种方法来解决 $self->compare() 子例程,但我运气不佳.

有什么想法吗?

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

解决方案

您有两个问题.首先,您需要超类中的 mySort 函数来为正确的子类调用 compare 函数.其次,您需要子类中的 compare 函数,以便能够从不同包中的调用接收要比较的两个元素.

不清楚你是否找到了第一个问题的解决方案,但一个解决方案是使用UNIVERSAL::can来找出正确的比较方法.

打包水果;子排序{我的 $self = shift;我的 $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 函数必须为此做好准备.这里有几个解决方案:

包装苹果;子比较{包装水果;$a->{mass} <=>$b->{质量};}

子比较{$Fruit::a->{mass} <=>$水果::b->{质量}}

或者更具防御性,

包装苹果;子比较{我的 $pkg = 来电者;如果 ($pkg ne __PACKAGE__) {没有严格的参考";$a = ${"${pkg}::a"};$b = ${"${pkg}::b"};}$a->{mass} <=>$b->{质量}}

<小时>

更新:我考虑过创建一个子程序属性,将 $a$b 值复制到正确的包中,但经过基准测试它并考虑替代方案,我决定反对.这是我对后代的结果:

考虑三个排序例程(可能在另一个包中并且难以从当前包中使用)

sub numsort { $a <=>$b }子词法排序 { $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;排序 numsort @input };

这里是基准测试结果.local 方法是普通的 sort 调用,没有跨包问题.

<前>速率 attrib-numsort wrap-numcmp local-numsort repkg-numsortattrib-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-lexsortattrib-lexsort 3.17/s -- -12% -14% -17%repkg-lexsort 3.60/s 13% -- -2% -5%包裹 lexcmp 3.68/s 16% 2% -- -3%本地词法排序 3.80/s 20% 6% 3% --速率 attrib-objsort wrap-objcmp local-objsort repkg-objsortattrib-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 调用中,性能下降还不算太糟糕,它在对象层次结构中工作,所以最终建议是:

打包水果;子比较{ ... }子排序{我的 $self = shift;@{$self->{itemList}} =sort { $self->can("compare")->($a,$b) } @{$self->{itemList}};}包装苹果;我们的@ISA = qw(水果)子比较 { $_[0]->{mass} <=>$_[1]->{质量}}

I want to use a superclass sort which uses a subclass compare function. I've tried to distill the nature of the question in the following code. This isn't the "production" code, but is presented here for illustration. It's tested.

#!/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;

What I want to do is to move mySort() to the abstract superclass Fruit. I've tried a number ways of addressing the $self->compare() subroutine, but I'm not having much luck.

Any thoughts?

I've gotten it to call the correct subroutine, but never with the correct $a and $b. I've left all of my failed attempts out of this question in the hopes that someone will know right away how to move the mySort() to the Fruit package so that I can sort my oranges with the same subroutine.

解决方案

You've got two problems. First, you need the mySort function in the super class to call the compare function for the correct subclass. Second, you need the compare function in the subclass to be able to receive the two elements it wants to compare from a call in a different package.

It's not clear whether you worked out a solution to the first problem, but one solution is to use UNIVERSAL::can to find out the right comparison method.

package Fruit;
sub mySort {
    my $self = shift;
    my $compare_func = $self->can("compare");
    @{$self->{itemList}} = sort $compare_func @{$self->{itemList}};
}

This will find the correct subclass compare function and use it in the sort call.

Now the issue in the Apples::compare function will be that when Fruit::mySort is ready to compare a couple of elements, it will set the package variables $Fruit::a and $Fruit::b, not $Apples::a and $Apples::b. So your Apples::compare function must be prepared for this. Here are a couple of solutions:

package Apples;
sub compare {
    package Fruit;
    $a->{mass} <=> $b->{mass};
}

or

sub compare {
    $Fruit::a->{mass} <=> $Fruit::b->{mass}
}

or more defensively,

package Apples;
sub compare {
    my $pkg = caller;
    if ($pkg ne __PACKAGE__) {
        no strict 'refs';
        $a = ${"${pkg}::a"};
        $b = ${"${pkg}::b"};
    }
    $a->{mass} <=> $b->{mass}
}


Update: I thought about making a subroutine attribute that would copy $a and $b values into the correct package, but after benchmarking it and thinking about alternatives, I decided against it. Here were my results for posterity:

Consider three sort routines (that might be in another package and hard to use from the current package)

sub numsort { $a <=> $b }
sub lexsort { $a cmp $b }
sub objsort { $a->{value} <=> $b->{value} }

Here are some ways we can make these packages accessible:

  1. implement a subroutine attribute to prepare the $a and $b variables in the right package. Implementation is too long to include here, but the sub declaration would look like

    sub numsort : CrossPkg { $a <=> $b }
    

  2. rewrite the comparison function to compare $_[0] and $_[1] instead of $a and $b, and use a wrapper in the sort call

    sub lexcmp { $_[0] cmp $_[1] }
    ...
    @output = sort { lexcmp($a,$b) } @input
    

  3. Perform the sort call in the correct package, so it sets the correct $a and $b values.

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

And here are the benchmarking results. The local method is the ordinary sort call with no cross-package issues.

                 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%            --

Summary: overhead is less of a concern with lexsort, where each comparison takes more time. The attribute approach is dead on arrival. Setting the package going into the sort call has the best results -- more or less no overhead -- but it isn't suitable for this application (in an object hierarchy). Rewriting the comparison function and wrapping the function in the sort call isn't too bad of a performance drop-off, and it works in an object hierarchy, so the final recommendation is:

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} }

这篇关于我可以在 perl 中调用带有子类比较的超类排序吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆