Arbitrary sorting

Ovid on 2005-11-28T17:40:19

One problem I sometimes get hit with is needing to order things in a rather arbitrary fashion. This is annoying so I wrote the following code to order a list of hashes by a particular key according to their position in another list:

#!/usr/bin/perl

use strict;
use warnings;
use Data::Dumper;

sub sort_order {
    my @names = @_;
    my %order_by = map { $names[$_] => $_ } 0 .. $#names;
    if ( keys %order_by ne @names ) {
        warn "duplicate names found";
    }
    return \%order_by;
}

my @list = (
    { name => 'foo' },
    { name => 'bar' },
    { name => 'baz' },
    { name => 'alpha' },
);

my $order_by = sort_order( qw/bar alpha foo baz/ );
@list = sort { $order_by->{$a->{name}} <=> $order_by->{$b->{name}} } @list;
print Dumper \@list;

This is rather useful, but I really want to be able to generalize this. However, sort takes a subname or a block, not an anonymous subroutine. I could do nasty things like have the sub install a sub in a symbol table and return the fully qualified name for the sort routine. Hmm, never thought of that.

Hell, now that I think of it, I could make what's returned an object. When used as a string it's the sort sub name and when it falls out of scope the subroutine is deleted. I'd just have to ensure this goes into some namespace which is documented to not be safe to use. In fact, this might be a nice, general purpose technique for getting around the "no anonymous sub" limitation of sort.


fwiw ...

Limbic Region on 2005-11-28T17:49:17

if ( keys %order_by ne @names ) {

# should probably be

if ( keys %order_by != @names ) {

If you didn't mind the performance hit of a tied hash, you could use my Tie::Hash::Sorted

You could combine your order sub and your sort routine in 1 by using lexicals and references. This way all you would need to do to change the sort order would be to change the array containing the list of keys - the rest would automagically happen. It would also allow you to add/delete/modify keys and values and always have the resulting order be what you want.

  First, you could combine

Re:fwiw ...

Ovid on 2005-11-28T17:58:11

Yup. That's a bug in my code, thanks :)

I can't use Tie::Hash::Sorted. I'm getting the hash from an external source. Thanks for the idea, though.

Re:fwiw ...

Limbic Region on 2005-11-28T18:27:11

Well, I am certainly not trying to push Tie::Hash::Sorted as I have never found a use for it myself. I wrote it because my efforts to contact Casey to correct the glaring problems of Tie::SortHash went unanswered (I later learned my emails went to a bit bucket). The original motivation was because it solved a problem a monk was having.

It does allow you to use an existing hash without damaging it in any way (it makes a copy).

Sounds like....

Simon on 2005-11-28T18:51:33

Sort::ArbiLex? That's helped me out for this sort of thing innumerable times.

File::Sort

pudge on 2005-11-29T02:28:44

Years and years ago I wrote File::Sort (which became a sort(1) for the Perl Power Tools project), and it has some stub function names in the File::Sort namespace that it modifies on the fly. I actually put together a sort subroutine as a string then eval it, and assign the result to *sortsub or whatever.

Passing sort subs

osfameron on 2005-11-29T08:31:40

You can also accept a subref, and do something like
sort { $mysub->($a, $b) }
That's slower, though my benchmarks didn't make it that much slower (though I may be wrong on that, writing good benchmarks is an art).

Anon subs work in sort as of 5.6.2

kappa on 2005-12-07T15:10:04

As far as I understand perlfunc, sort accepts refs to anonymous subs. And I use such code all the time:

sort $sorter{$field}, @objs;

Re:Anon subs work in sort as of 5.6.2

kappa on 2005-12-07T15:12:20

Sorry, stray comma.

Re:Anon subs work in sort as of 5.6.2

Ovid on 2005-12-07T17:27:21

Oops. You're quite right.

perl -Mstrict -le 'my $x= sub{$b<=>$a};print sort $x qw/3 1 2/;'

I tried it along time ago and found out it didn't work. I suppose I should have tried again :)