develooper Front page | perl.beginners | Postings from April 2003

Re: multiple sorts

Thread Previous | Thread Next
From:
Rob Dixon
Date:
April 27, 2003 04:49
Subject:
Re: multiple sorts
Message ID:
20030427114920.30143.qmail@onion.perl.org
Hi Kevin.

Kevin Pfeiffer wrote:
> Hi,
>
> Following recommendations in the Cookbook,etc., I have an array of arrays of
> columns of tabular data on which I do a primary sort and then a secondary
> sort, somewhat like this:
>
> @sortedAoA = sort { $a->[name] cmp $b->[name]
>                            ||
>                   $b->[age] <=> $b->[age] } @AoA;
>
> (I hope my simplification didn't introduce any errors here)

I think you have an array of hashes, where each element
of the array (@AoH?) is an anonymous hash reference. So
I guess that should be

    @sortedAoH = sort {
        $a->{name} cmp $b->{name} ||
        $b->{age} <=> $b->{age}
    } @AoH

> This seemed fine and is simple, so far, but I am sorting on columns
> containing data that could be numbers or letters, so I then needed to
> account for things like 'if($col_is_number)' to choose the correct
> operator; for two sorts this gave me four variations (not counting
> 'reverse').

You want to sort data stringwise unless it is all numeric, when it should
be sorted numerically?

> What if I want the option now of sorting on three or more columns? This
> seems to become rather convoluted after a while and my sort subroutine is
> 70 lines long (I'm also doing transliteration of "รค" to "ae" for the sort,
> etc., which will be the subject of a future sort question).
>
> Instead of doing these 'sorts within sorts' couldn't I set up multiple,
> repeated sorts for each column, working backwards? So if I want to sort on
> country, then city, then name, I first do a sort on name, save results to
> array, then do a sort on city using this newly-sorted array, save, and then
> do a final sort on country.
>
> This may take longer, but I'm dealing with perhaps at most a couple three
> hundred lines of text, and it would easily let me set up a loop which gets
> the column number to sort on from user(be it 2, 3, or more) and then run
> the sorting loop as described.
>

I'm afraid you can't in general, because you need a sort routine that
guarantees that data with equal sort keys stay in the same order. The
Perl 'sort' function doesn't do this.

The obvious solution is to provide a subroutine which will compare a
pair of anonymous data numerically if both values are numbers, or
stringwise if at least one is non-numeric. Like this

  sub compare {
    my ($a, $b) = @_;
    ($a =~ /\D/ or $b =~ /\D/) ?
    $a cmp $b :
    $a <=> $b
  }

Then you can just rewrite you sort call as below.

I hope this helps.

Rob



my @data = (
  { name => 'aaa', age => 1  },
  { name => 'bbb', age => 7  },
  { name => 'aaa', age => 11 },
  { name => 'bbb', age => 4  },
  { name => 'aaa', age => 9  },
  { name => 'bbb', age => 13 },
  { name => 'aaa', age => 8  },
  { name => 'bbb', age => 12 },
  { name => 'aaa', age => 22 },
  { name => 'bbb', age => 18 },
  { name => 'aaa', age => 24 },
  { name => 'bbb', age => 44 },
  { name => 'aaa', age => 30 },
  { name => 'bbb', age => 51 },
  { name => 'aaa', age => 66 },
  { name => 'bbb', age => 60 },
);

@data = sort {
  compare($a->{name}, $b->{name}) ||
  compare($a->{age}, $b->{age})
} @data;

print $_->{name}, ": ", $_->{age}, "\n"
    foreach @data;

>>OUTPUT<<

aaa: 1
aaa: 8
aaa: 9
aaa: 11
aaa: 22
aaa: 24
aaa: 30
aaa: 66
bbb: 4
bbb: 7
bbb: 12
bbb: 13
bbb: 18
bbb: 44
bbb: 51
bbb: 60




Thread Previous | Thread Next


nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About