dsdisc has asked for the wisdom of the Perl Monks concerning the following question:

Hello All, I want to sort the values of anonymous hashes contained in an array, but the values of each hash contain either strings OR numbers. So I have a data structure like: @data = ( { name => "bob", age => 18, }, { name => "jim", age => 24, } ); Obviously if I sort on "name", they are strings and if I sort on "age" they are numbers. The best solution I've come up with so far is: my $field = "name"; ## or $field = "age"; @sorted = sort { if ($a->{$field} =~ /a-zA-Z/){ $a->{$field} cmp $b->{$field} }else{ $a->{$field} <=> $b->{$field} } } @data; This works, but I'm wondering if there is a less ugly way to do it. Thanks.

Replies are listed 'Best First'.
Re: Advanced Sorting
by turnstep (Parson) on Apr 20, 2000 at 01:59 UTC
    It's hard to see what you are asking. If you are trying to sort by name and then age, your solution does not quite work. Here's some code to do that:
    @sorted = sort { unless ($a->{'name'} cmp $b->{'name'}) { $a->{age} <=> $b->{age} } } @data;
    I used the following list:
    jim 18 bob 24 bob 14 cathy 98 cathy 45
    Which gets sorted to:
    bob 14 bob 24 cathy 45 cathy 98 jim 18
      Thanks for the reply, but that's not quite what I need. I need to sort on values that may be all strings OR all numbers, not a primary then secondary search. And one small optimization to your code would be:
      @sorted = sort { <BR> $a->{'name'} cmp $b->{'name'} || $a->{age} <=> $b->{age} <BR> } @data; <BR>
      Perl's sorting algorithm will return '0' if the "cmp" is equal, defaulting to the secondary sort.
Re: Advanced Sorting
by snowcrash (Friar) on Apr 20, 2000 at 12:43 UTC
    just another one that might speed up sorting if the sort criteria would get more complicated...
    my @sorted = map { $_->[2] } sort { ($a->[0] cmp $b->[0]) || ($a->[1] cmp $b->[1]) } map { [ $_->{name}, $_->{age}, $_ ] } @data;
Re: Advanced Sorting
by btrott (Parson) on Apr 20, 2000 at 02:41 UTC
    You could try something like this, though I'm not sure it's not uglier than what you have. :) Although this doesn't have a conditional in the sort-block, so it should be faster, I think.
    my $field = "name"; sub numsort { $a->{$field} <=> $b->{$field} } sub alphasort { $a->{$field} cmp $b->{$field} } my @data = ( { name => 'jim', age => 16 }, { name => 'bob', age => 18 }, ); my $sortsub = $field eq "name" ? 'alphasort' : 'numsort'; my @sorted; { no strict 'refs'; @sorted = sort $sortsub @data; }
    You need the "no strict 'refs'" because that
    sort $sortsub @data
    is actually using symbolic references, which is ugliness itself. :)

    Can sort subs be sub references? From the docs I looked at, it doesn't seem like they can. If they could, that would be a *much* cleaner way of doing it.

      They sure can:
      #!/usr/bin/perl -w use strict; my @data = ( { name => 'jim', age => 16 }, { name => 'bob', age => 18 }, ); sub sort_me { my $field = shift; if ($field eq "name") { return sub { $a->{$field} cmp $b->{$field} }; } else { return sub { $a->{$field} <=> $b->{$field} }; } } my @sorted; my $type = sort_me("name"); @sorted = sort $type @data; $type = sort_me("age"); @sorted = sort $type @data;
      Closures are so nice.
RE: Advanced Sorting
by dsdisc (Initiate) on Apr 20, 2000 at 04:04 UTC
    Thanks turnstep, btrott and chromatic!! Now it might get more complicated, or at least messy if I had multiple keys in the hashes, some of which were strings and some of which were numbers. Like in addition to age and name if I had Last Name (string), salary (number), car (string), model year (number), etc. Then I would have to test for every one. That's why I used a reg exp in the first place. But I like the idea of testing outside of the sort for efficiency, I will definitely use that.
      You could use a regex to test for appropriate key names. Or you could do some testing with my technique (returning an anonymous sub) and change the test to see if the datatype is a number if you take a look at How to identify a number datatype in a string?. It's probably lots simpler just to keep a data structure of appropriate fields guaranteed to hold strings or numbers, though.
Re: Advanced Sorting
by turnstep (Parson) on Apr 20, 2000 at 02:37 UTC
    In that case, the only optimization to your code I can think of is:
    if ($field eq "name") { $a->{$field} cmp $b->{$field} } else { $a->{$field} <=> $b->{$field} }
    which stops the code from examining every value twice, but simply evaluates a scalar. It benchmarks as slightly faster. Actually, for ultimate speed, move the field check before the sort subroutine is called:
    if ($field eq "name") { @sorted = sort @data; } else { @sorted = sort {$a <=> $b} @data; }
    etc...

      I am just getting into perl,and have a basic simple question. I have written a script that reads in a directory of logfiles, sorts them in reverse chronological order, and outputs them into an html page. I cannot figure out how to sort the directory deeper than just the month, which comes first (the files in the dir are all listed by month/day/year). How can I sort this so the most recent days are first?
        1. read the perlman (or unixman) for the commands stat() and sort()
        2. try to understand the following :-)
        sub bydate{(stat $dir.$a)[9]<=>(stat $dir.$b)[9];} @sortedfiles = sort bydate @files_in_your_dir;
        Have a nice day
        All decision is left to your taste
        Update
        ok, I might have been wrong when assuming that logs get analyzed but not changed, after they have been archived