Dave05 has asked for the wisdom of the Perl Monks concerning the following question:
Monks,
I want to do complex sorts on hash values, so I am using sort subroutines to do the comparisons. Although this works fine, I would like to move the 'up' and 'down' subs outside the scope of the 'sort_em' sub and pass in the %nos
hash explicitly. This would make the sorting subs
much more re-usable because I could then access them from different places, I could even hide them away in a module. Is this possible?
#!/usr/bin/perl
# sort_em.plx
use warnings;
use strict;
my %numbers = qw(one 1 two 2 three 3 four 4 five 5 six 6 seven 7 eight
+ 8);
sort_em(\%numbers);
sub sort_em {
our %nos = %{$_[0]};
print "UP:\n";
print join "\n", sort up keys %nos;
print "\n\nDOWN:\n";
print join "\n", sort down keys %nos;
sub up { $nos{$a} <=> $nos{$b} }
sub down { $nos{$b} <=> $nos{$a} }
}
Re: Scoping issue when sorting with subroutines
by perlplexer (Hermit) on Apr 12, 2002 at 14:59 UTC
|
Yes, you can do that; however, you should have a darn good reason for doing so. Why? Because your code will be calling
a sub for each and every comparison, which is rather expensive and will slow down your program.
use warnings;
use strict;
my %numbers = qw(one 1 two 2 three 3 four 4 five 5 six 6 seven 7 eight
+ 8);
sort_em(\%numbers);
sub sort_em {
our %nos = %{$_[0]};
print "UP:\n";
print join "\n", sort { up(\%nos) } keys %nos;
print "\n\nDOWN:\n";
print join "\n", sort { down(\%nos) } keys %nos;
}
sub up { $_[0]->{$a} <=> $_[0]->{$b} }
sub down { $_[0]->{$b} <=> $_[0]->{$a} }
--perlpelxer | [reply] [d/l] |
Re: Scoping issue when sorting with subroutines
by Fletch (Bishop) on Apr 12, 2002 at 15:16 UTC
|
Keep in mind that perl currently doesn't do nested subs
(this is the souce of many a warning for those using Apache::Registry with mod_perl; see the mod_perl guide
for a detailed explanation).
You could possibly return a list of coderefs that you create and use sort_em more as a sort routine factory.
sub sort_em {
my $src = shift;
return {
up => sub { $src->{$a} <=> $src->{$b} },
down => sub { $src->{$b} <=> $src->{$a} },
}
}
my %numbers = ( qw( a 5 c 4 d 1 ) );
my $num_sorts = sort_em( \%numbers );
print "UP:\n",
join( "\n", map { "$_ $numbers{$_}" }
sort { $num_sorts->{up}->() } keys %numbers ),
"\nDOWN\n",
join( "\n", map { "$_ $numbers{$_}" }
sort { $num_sorts->{down}->() } keys %numbers ),
"\n";
| [reply] [d/l] [select] |
|
> Keep in mind that perl currently doesn't do nested subs
Ack, how un-DWIM!
sub foo {
my $x = "a var in foo()";
sub bar {
print "\$x is - [$x]\n";
}
}
bar();
__output__
$x is - []
But if we use a closure, all is well with the scope of $x
sub foo {
my $x = "a var in foo()";
return sub {
print "\$x is - [$x]\n";
}
}
foo()->();
__output__
$x is - [a var in foo()]
I was vaguely aware of the fact that nested subs in perl were broken (i.e scoped to current package, not current sub) and now I know the full extent (but I guess the logic should've followed really). Oh well, not much longer til Perl6 anyway ...
HTH
broquaint | [reply] [d/l] [select] |
Re: Scoping issue when sorting with subroutines
by strat (Canon) on Apr 12, 2002 at 14:58 UTC
|
If you are using a "global" variable (with use vars or our in perl5.6), you can accept it via $packagename::nos{$a}.
If %nos is a local-Variable (my), you have to give it (or better a reference to it) to the subs &up and &down.
Best regards,
perl -le "s==*F=e=>y~\*martinF~stronat~=>s~[^\w]~~g=>chop,print" | [reply] |
Re: Scoping issue when sorting with subroutines
by erikharrison (Deacon) on Apr 12, 2002 at 15:07 UTC
|
Sure it is! Just put the sub declarations in another package, use package at the top of your main code, and then use a fully qualified package name in place of up and down. However, since the code for these two subs is small, and refers to a specific variable in a wider scope, this may not be a good idea. Here is a rewrite of the sort_em sub that may point out why.
sub sort_em {
my %nos = @_; #Don't make a ref just to deref it, just
#pass the hash. Use 'my' to indicate
#lexical scope
print "UP:\n";
print join "\n", sort { $nos{$a} <=> $nos{$b} } keys %nos; #just p
+ass in short sorting routines on the line
#of the sort
print "\n\nDOWN:\n";
print join "\n", sort { $nos{$b} <=> $nos{$a} } keys %nos; #same a
+s above
}
Note that the declaration of the subs up and down inside the braces of sort_em does not make them lexically scoped.
Cheers,
Erik | [reply] [d/l] [select] |
Re: Scoping issue when sorting with subroutines
by extremely (Priest) on Apr 12, 2002 at 19:17 UTC
|
Maybe something like this is what you wanted? It is rather on
the dark side of the force but it may be what you want.
#!/usr/bin/perl -w
use strict;
my @l = (2, 4, 19, 3, 15, 30, 1, 31, 14);
print sort {&{bysort("up")}} @l;
print $/;
print sort {&{bysort("dn")}} @l;
print $/;
sub bysort {
my $by = shift;
if ($by eq "up") {
return sub { $a <=> $b };
} elsif ($by eq "dn") {
return sub { $b <=> $a };
} else {
return sub { $a cmp $b };
}
}
--
$you = new YOU;
honk() if $you->love(perl) | [reply] [d/l] |
Re: Scoping issue when sorting with subroutines
by Dave05 (Beadle) on Apr 12, 2002 at 17:10 UTC
|
print join "\n", sort up(\%nos) } keys %nos;
which is cool. Unfortunately, in stripping down my code to present it to the Monks, I actually stripped out the main source of my problem (doh).
The reason I want to use a sort subroutine is that I don't know how I want to sort the thing until runtime. So I want to call my routine like this:
foreach my $id (sort $by_sort_method keys %contents) {
And I'd like to pass the %contents hash to the subroutine.
How do I do that?
| [reply] [d/l] [select] |
|
Use something like this to create $by_sort_method:
sub create_sorter
{
my $contents = shift;
return sub {
# sort however you want here
$contents{$a}{FOO} cmp $contents{$b}{FOO}
};
}
my $by_sort_method = create_sorter \%contents;
foreach my $id (sort $by_sort_method keys %contents) {
...
}
| [reply] [d/l] [select] |
|
|