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

I have a 2 dimensional list that I need to rank (descending numerical order). For example I have these variables and values:
$total[1][1] = 123 $total[2][1] = 345 $total[3][1] = 222
From this I need to store a value in another 2D list:
$rank[1][1] = 1 $rank[2][1] = 3 $rank[3][1] = 2
The second index of $total had the highest number, so the second index of $rank gets the highest rank of '3' My list is 7x15 so I need to rank 7 values 15 times. How can I do this?

Replies are listed 'Best First'.
Re: I need to sort a 2D list.
by iakobski (Pilgrim) on May 03, 2001 at 14:14 UTC
    What you need to do is to put the numbers into a structure that can be sorted while keeping track of the positions of the values in the original 2D array.

    A hash is ideal for this, store the array indexes as an anonymous array in the hash value. Then sort on the hash key and that will give you the rank.

    This was my first attempt:

    use warnings; use strict; my @total; $total[1][1] = 123; $total[2][1] = 345; $total[3][1] = 222; # hash of arrays: hash{ number } = [ i, j ] my %hash; for my $i ( 1 .. $#total ) { for my $j ( 1 .. $#{$total[$i]} ) { $hash{ $total[$i][$j] } = [ $i, $j ]; } } my @rank; my $ranking = 0; foreach my $value ( sort keys %hash ){ my ( $i, $j ) = @{$hash{ $value }}; $rank[$i][$j] = ++$ranking; } for my $i ( 1 .. $#rank ) { for my $j ( 1 .. $#{$rank[$i]} ) { print "rank[$i][$j] is $rank[$i][$j]\n"; } }
    and here is the output:

    rank[1][1] is 1 rank[2][1] is 3 rank[3][1] is 2
    But then I realised that this won't work if a value appears more than once: the hash element is overwritten. We need a little more intelligence, here's my second attempt:
    use warnings; use strict; my @total; $total[1][1] = 123; $total[2][1] = 345; $total[3][1] = 222; $total[4][1] = 222; # hash of arrays: hash{ number } = [ i, j ] my %hash; for my $i ( 1 .. $#total ) { for my $j ( 1 .. $#{$total[$i]} ) { push @{$hash{ $total[$i][$j] }}, $i, $j; } } my @rank; my $ranking = 0; foreach my $value ( sort keys %hash ){ ++$ranking; while ( my ($i, $j ) = splice @{$hash{ $value }}, 0, 2 ){ $rank[$i][$j] = $ranking; } } for my $i ( 1 .. $#rank ) { for my $j ( 1 .. $#{$rank[$i]} ) { print "rank[$i][$j] is $rank[$i][$j]\n"; } }
    which gives:
    rank[1][1] is 1 rank[2][1] is 3 rank[3][1] is 2 rank[4][1] is 2

    As an aside, you are the second poster today I have seen starting arrays at 1 - doing this is a bit confusing, and also you still get $array[0] created automatically and set to undef. This then gives you extra values if you do something like print @array.

    -- iakobski

      Thank you for the information. It will work well. However, I forgot to mention that if two value are equal, they take the next two ranks and divide by two. For example:
      $total[1][1] = 123; $total[2][1] = 345; $total[3][1] = 222; $total[4][1] = 222; rank[1][1] should be 1 rank[2][1] should be 4 rank[3][1] should be 2.5 #(3+2)/2 rank[4][1] should be 2.5 #(3+2)/2
      How can I do this?
        Well that should be easy - instead of incrementing by one each time through the loop, you need to count the number of values at that rank. Then do a separate increment before and after using the rank.

        I reckon for num values at the given rank you would need (num - 1)/2 as the post-increment and (num - postincrement) as the pre-increment, but you know what you are trying to achieve.

        -- iakobski

Answer: I need to sort a 2D list.
by runrig (Abbot) on May 03, 2001 at 09:39 UTC
    I suspect this could be somehow shorter and trickier, but here's something fairly straight forward:
    use strict; my @totals = ([ 3, 4 ],[ 1, 2 ]); my %hash; for my $i (0..$#totals) { my $aref = $totals[$i]; for my $j (0..$#{$totals[$i]}) { $hash{"$i,$j"}=$totals[$i][$j]; } } my @sorted = sort { $hash{$a} <=> $hash{$b} } keys %hash; my @ranks; my $rank = 0; for (@sorted) { my ($i, $j) = split(/,/); $ranks[$i][$j] = ++$rank; } for my $i (0..@ranks) { for my $j (0..$#{$ranks[$i]}) { print "$i $j $ranks[$i][$j]\n"; } }
Re (tilly) 1: I need to sort a 2D list.
by tilly (Archbishop) on May 03, 2001 at 21:17 UTC
    Depending on your exact problem, you may not need rank at all. In C you often want to sort data by sorting an array of pointers and then referring to thinks indirectly through the sorted array. Perl arrays are already arrays of pointers, so it makes more sense in Perl just to sort the original array directly. (Untested code ahead.)
    @sorted = sort {$b->[1] <=> $a->[1]} @total;
    In general direct logic for accessing things by position is un-Perlish and it is worth trying to see if you can (not that you always can) avoid it...