3

I have a data structure like the following

@colors = qw(red blond green);
@numbers = qw(349 1234.5678 3.14159265);
@hats = qw(fedora porkpie bowler);
my %hash = (colors => \@colors, numbers => \@numbers, hats => \@hats);

I want to sort this according to the values of one of the arrays, maintaining the association of parallel array elements. That is, if I exchange $hash{numbers}[2] and index $hash{numbers}[3], i want to do the same exchange for all the other arrays in the hash. In this case, if I sort {$a <=> $b} on numbers:

$sorted{numbers} = [3.14159265, 349, 1234.5678];
$sorted{colors}  = ["green", "red", "blond"];
$sorted{hats}  = ["bowler", "fedora", "porkpie"];

The solution i'm using now inverts the structure of %hash into an array where $array[$i]{$k} == $hash{$k}[$i], does @sorted = sort {$a->{numbers} <=> $b->{numbers}} @array, then converts @sorted back from array of hashes to hash of arrays.

I don't really care if the sort is stable, I just wonder if there's a better way to do it.

flies
  • 2,017
  • 2
  • 24
  • 37
  • btw I found other similar questions ( http://stackoverflow.com/questions/762399/sort-by-value-hash-of-hash-of-hashes-perl http://stackoverflow.com/questions/827105/how-can-i-sort-perl-hashes-whose-values-are-array-references ) but I don't think this is the same thing. – flies Oct 26 '10 at 18:36
  • Transform the rows to columns and then sort? e.g. `[[red 349 fedora], ...]` The general data-structure looks hard to deal with. I'd likely change it. –  Oct 26 '10 at 18:37
  • @pst that is the solution i describe in my question. – flies Oct 26 '10 at 18:52
  • 1
    You may want to consider the difficulty in the two choices though. This is a somewhat complicated question, as shown by the fact that you chose to ask. However, were you to store them as an array of hashes when you wanted to pass it off you could just do: map { $_->{hat} } @data. That seems a lot simpler to work with. – mfollett Oct 27 '10 at 02:12

1 Answers1

10

Here's a trick I've used.

my @permutation = sort { $numbers[$a] <=> $numbers[$b] } (0..$#numbers);
@colors = @colors[@permutation];
@numbers = @numbers[@permutation];
@hats = @hats[@permutation];
# No change to %hash needed, since it has references to above arrays.
aschepler
  • 70,891
  • 9
  • 107
  • 161
  • 2
    I think you'd be better off rethinking your data structure, e.g. using an array of hashes instead of a hash of arrays. But if you must sort "records" split across multiple arrays, this is the way to do it. – cjm Oct 26 '10 at 18:47
  • I asked my colleague who uses python and this was his suggestion, only in python he would use `argsort` to do it. – flies Oct 26 '10 at 18:56