views:

107

answers:

4

Hi,

I have a few arrays of the same length. I want to sort the first array, and make all the others array "sort" accordingly. For example, if the first array is (7,2,9) the second is ("seven","two","nine") and the third is ("VII","II","IX") after the sort (ascendingly according to the first array values) we will have (2,7,9) ("two","seven","nine") and ("II","VII","IX").

How can I do that?

+5  A: 

Re-organize the data to a single array for sorting:

my @a = ([7, "seven", "VII"], [2, "two", "II"], ..);
@a = sort { $a->[0] <=> $b->[0] } @a;

Then recreate the original arrays:

my(@a1, @a2, @a3);

for (@a) {
    push @a1, shift @$_;
    push @a2, shift @$_;
    push @a3, shift @$_;
}  
eugene y
+4  A: 

As you are discovering, maintaining parallel arrays can be a hassle and error prone. An alternative approach is to keep related information together.

use strict;
use warnings;

# One array-of-hashes instead of three parallel arrays.
my @numbers = (
    { arabic => 7, text => 'seven', roman => 'VII' },
    { arabic => 2, text => 'two',   roman => 'II'  },
    { arabic => 9, text => 'nine',  roman => 'IX'  },
);

@numbers = sort { $a->{arabic} <=> $b->{arabic} } @numbers;
FM
+12  A: 

While I agree with eugene y and MvanGeest that usually the best answer is to switch to another data structure, sometimes you might want parallel arrays (or at least, might not be able to avoid them), and there actually is a way to sort parallel arrays in parallel. It goes like this:

my @nums = (7, 2, 9);
my @names = qw(seven two nine);
my @roman = qw(VII II IX);

my @sorted_indices = sort { $nums[$a] <=> $nums[$b] } 0..$#nums;
@$_ = @{$_}[@sorted_indices] for \(@nums, @names, @roman);

That is, generate a list of the indices that correspond to all of the arrays, and then sort them according to the order that will put the "primary" array in order. Once we have the sorted list of indices, re-order all of the arrays to match.

The final line could be written out longhand as

@nums = @nums[@sorted_indices];
@names = @names[@sorted_indices];
@roman = @roman[@sorted_indices];

but I tried to reduce the amount of copy-paste necessary, even at the cost of some slightly hairy syntax. The More You Know...

hobbs
+1 Slick. Suggests the idea of a general function along these lines: `sub sort_parallel_arrays { my ($comparator, @arrays) = @_; ... }`.
FM
+1 Very cool indeed!
David B
+3  A: 

I know you've already accepted an answer, and there are other really good answers here, but I would propose something different: don't duplicate your data. You only need to keep track of the arabic -> roman mapping once -- why store what are essentially duplicate arrays of numbers, and sort every one? Just sort the master list and look up the other values in a reference array as needed:

my @roman = qw(0 I II III IV V VI VII VIII IX X);
my @text = qw(zero one two three four five six seven eight nine ten);

my @values = (7, 2, 9);
my @sorted_values = sort @values;
my @sorted_roman = map { $roman[$_] } @sorted_values;
my @sorted_text = map { $text[$_] } @sorted_values;

use Data::Dumper;
print Dumper(\@sorted_values, \@sorted_roman, \@sorted_text);

prints:

$VAR1 = [
          2,
          7,
          9
        ];
$VAR2 = [
          'II',
          'VII',
          'IX'
        ];
$VAR3 = [
          'two',
          'seven',
          'nine'
        ];

In a real environment, I would suggest using libraries to perform the Roman and textual conversions for you:

use Roman;
my @sorted_roman = map { roman($_) } @sorted_values;

use Lingua::EN::Numbers 'num2en';
my @sorted_text = map { num2en($_) } @sorted_values;
Ether
+1: That's quite similar to hobbs' suggestion, but with a cleaner syntax, isn't it?
David B
@David: not really; hobbs's approach still stores the lists of numbers in each format, sorts the numeric ones and then uses the indexes of those as a guide to sort the others. I abandon storing lists of the other values entirely, and simply look them up from a master conversion table (or external conversion module) afterwards. If you're sorting things that need to be sorted at the same time, but you can't easily convert from one to the other (i.e. you need to store both versions), hobbs's approach is the way to go, but otherwise, use a lookup table.
Ether