Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

Comparing spaceships (cmp and <=> as options)

by Random_Walk (Parson)
on May 20, 2013 at 15:31 UTC ( #1034352=perlquestion: print w/ replies, xml ) Need Help??
Random_Walk has asked for the wisdom of the Perl Monks concerning the following question:

Fellow Monks, I seek your Perls of wisdom.

I have an array of arrays. the lower level arrays are records with values both numeric and alpha. I want to give the user the option to sort on any of these fields. I thought it would be nice to use a simple hash table referencing the record number to sort on and the comparator to use. I am having a problem using a variable containing comparator. Am I missing something or do I just need another approach?

my $sort = 'hours'; # really comes from a switch my %map = ( # sorting map hours => [0, \sub {<=>}], code => [1, \sub {cmp}], name => [2, \sub {cmp}], ); # example data my @records = ( [10, 'xyz232', 'secret project'], [ 5, 'foo123', 'world domination'], [ 7, 'bar666', 'have a beer'], ); for ( sort {$a->[$map{$sort}->[0]] $map{$sort}->[1] $b->[$map{$sort}->[ +0]]} @records ) { print join ", ", @$_; }

Update

among many variations of syntax I also tried the following. I feel it may be getting closer, other than the fact it won't compile :)
my %map = ( hours => sub {sort { $_[0] <=> $_[0] } };, number => sub {sort { $_[0] <=> $_[0] } };, name => sub {sort { $_[0] <=> $_[0] } };, task => sub {sort { $_[0] <=> $_[0] } };, );

Cheers,
R.

Pereant, qui ante nos nostra dixerunt!

Comment on Comparing spaceships (cmp and <=> as options)
Select or Download Code
Re: Comparing spaceships (cmp and <=> as options)
by moritz (Cardinal) on May 20, 2013 at 15:41 UTC
    One problem is that if you write
    \sub {<=>}
    , the <=> doesn't receive any arguments (and doesn't even parse the way you want it to, because the parser expects a term, but finds an operator instead). So you have to write something like

    sub { $_[0] <=> $_[1] }

    instead. This code seems to work:

    #!/usr/bin/perl use strict; use warnings; use 5.010; my $sort = 'hours'; # really comes from a switch my %map = ( # sorting map hours => [0, sub {$_[0] <=> $_[1]}], code => [1, sub {$_[0] cmp $_[1]}], name => [2, sub {$_[0] cmp $_[1]}], ); # example data my @records = ( [10, 'xyz232', 'secret project'], [ 5, 'foo123', 'world domination'], [ 7, 'bar666', 'have a beer'], ); my $elem = $map{$sort}[0]; my $sorter = $map{$sort}[1]; my @sorted = sort { $sorter->($a->[$elem], $b->[$elem]) } @records; say join "\t", @$_ for @sorted;
Re: Comparing spaceships (cmp and <=> as options)
by choroba (Abbot) on May 20, 2013 at 15:54 UTC
    sub returns a code reference on its own, no need to add a backslash. You have to add parameters to the subs, though. The canonical ones for sort are $a and $b. Localize them to the value to be sorted on so that the code still returns the full record.
    #!/usr/bin/perl use warnings; use strict; my $sort = 'hours'; # really comes from a switch my %map = ( # sorting map hours => [0, sub {$a <=> $b}], code => [1, sub {$a cmp $b}], name => [2, sub {$a cmp $b}], ); # example data my @records = ( [10, 'xyz232', 'secret project'], [ 5, 'foo123', 'world domination'], [ 7, 'bar666', 'have a beer'], ); for ( sort { local ($a, $b) = map $_->[$map{$sort}->[0]], $a, $b; $map{$sort}[1]->(); } @records ) { print "@$_\n"; }
    لսႽ ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
Re: Comparing spaceships (cmp and <=> as options)
by LanX (Canon) on May 20, 2013 at 16:39 UTC
    And now to something completely different =)

    sort allows providing a function name

    sort SUBNAME LIST
    So using an ad-hoc package seems like a short way to solve this :)

    DB<138> sub sort::hours { $a->[0] <=> $b->[0] } DB<139> $sort = 'hours'; => "hours" DB<140> *sort::this=\&{"sort::$sort"} => *sort::this DB<141> sort sort::this @records => ( [5, "foo123", "world domination"], [7, "bar666", "have a beer"], [10, "xyz232", "secret project"], )

    I don't like the glob-manipulation in line 140, but it could be used to eval arbitrary complex sort routines.

    Maybe others have more elegant ideas! =)

    HTH!

    Cheers Rolf

    ( addicted to the Perl Programming Language)

Re: Comparing spaceships (cmp and <=> as options)
by tobyink (Abbot) on May 20, 2013 at 16:50 UTC

    Have you seen salva's Sort::Key and Sort::Key::Maker - they don't do exactly what you're talking about, but I'm fairly sure you'll find something to like in them.

    package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name
Re: Comparing spaceships (cmp and <=> as options)
by periapt (Hermit) on May 20, 2013 at 17:53 UTC
    Here is a version that uses a variant of the Schwartzian Transform.
    my @records = ( [10, 'xyz232', 'secret project'], [ 5, 'foo123', 'world domination'], [ 7, 'bar666', 'have a beer'], ); my %sort_this_way = ( 'hours' => [0, sub { return sort { $a->[0][$a->[1]] <=> $b->[0][$b->[1]]; } @_; }], 'code' => [1, sub { return sort { $a->[0][$a->[1]] cmp $b->[0][$b->[1]]; } @_; }], 'name' => [2, sub { return sort { $a->[0][$a->[1]] cmp $b->[0][$b->[1]]; } @_; }], ); my $sort = 'hours'; # really comes from a switch my @new = map { $_->[0] } $sort_this_way{$sort}->[1]( map { [ $_ , $sort_this_way{$sort}->[0], $_->[ $sort_this_way{$sort}->[0] ] ] } @records );
    Granted the sort part of the transform is actually a function call but I couldn't remember how to set up a function to not require parenthesis without defining a prototype first.


    PJ
    use strict; use warnings; use diagnostics;

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1034352]
Approved by moritz
Front-paged by ww
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others taking refuge in the Monastery: (3)
As of 2014-09-18 22:18 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    How do you remember the number of days in each month?











    Results (125 votes), past polls