2

I have a hash of arrays, and I need to sort it first on the keys, and then on the values in the array.

Here's my simple code:

my %myhash;
$line1 = "col1 0.999";
$line2 = "col2 0.899";
$line3 = "col2 -0.52";
$line4 = "col2 1.52";

#insert into hash
@cols = split(" ", $line1);
push @{ $myhash{$cols[0]} }, $line1;
@cols = split(" ", $line2);
push @{ $myhash{$cols[0]} }, $line2;
@cols = split(" ", $line3);
push @{ $myhash{$cols[0]} }, $line3;
@cols = split(" ", $line4);
push @{ $myhash{$cols[0]} }, $line4;

foreach $k (sort {$a <=> $b} (keys %myhash)) {
   foreach $v(sort {$a <=> $b}(@{$myhash{$k}}))
   {
       print $k." : $v \n";     
   }
}

But I get the following output:

col1 : col1 0.999
col2 : col2 0.899
col2 : col2 -0.52
col2 : col2 1.52

So the keys are sorted fine, but the values aren't. I need them to come out like this:

col1 : col1 0.999
col2 : col2 -0.52
col2 : col2 0.899
col2 : col2 1.52

What's wrong with my code?

user961627
  • 12,379
  • 42
  • 136
  • 210
  • 2
    Your values contain `col2 1.52`, with the col2 value included. This is what throws off the sorting. – Konerak Oct 27 '11 at 11:13
  • Why are you keeping the `colN` name in the values? What type of order do you need for the keys (numeric or string)? – Mat Oct 27 '11 at 11:14
  • This is why you should always use "use warnings". The error becomes obvious at that point as perl complains loudly about the mistake. – bot403 Oct 27 '11 at 13:25

2 Answers2

6

Not sure why you're building a hash. All you need is a quick Schwartzian Transform.

#!/usr/bin/perl

use strict;
use warnings;

my $line1 = "col1 0.999";
my $line2 = "col2 0.899";
my $line3 = "col2 -0.52";
my $line4 = "col2 1.52";

my @sorted = map { join ' ', @$_ }
             sort { $a->[0] cmp $b->[0] or $a->[1] <=> $b->[1] }
             map { [ split ] } ($line1, $line2, $line3, $line4);

print "$_\n" for @sorted;

Also, having variables called $lineX is a bit of a red flag. You should probably store those values in an array.

Konerak
  • 39,272
  • 12
  • 98
  • 118
Dave Cross
  • 68,119
  • 3
  • 51
  • 97
  • I do have the lines in an array, just testing out here. By the way this works great and is so much neater! Just a question though, how would it work in reverse? As in, descending order? – user961627 Oct 27 '11 at 13:40
  • If you want to reverse the order of one of the sort comparison, but not the other then just reverse the use of `$a` and `$b`. The easiest way to reverse the whole sort is to put `reverse` in front of the call to `sort`. – Dave Cross Oct 27 '11 at 13:55
  • Okay I think something went a little haywire.. I've re-posted the question here: http://stackoverflow.com/questions/7953491/perl-numerical-sort-of-arrays-in-a-hash-2-schwarzian-transform – user961627 Oct 31 '11 at 12:11
3

Are you sure you want the cols string in the values again? If not, try this:

my %myhash;
$line1 = "col1 0.999";
$line2 = "col2 0.899";
$line3 = "col2 -0.52";
$line4 = "col2 1.52";

#insert into hash
@cols = split(" ", $line1);
push @{ $myhash{$cols[0]} }, $cols[1];
@cols = split(" ", $line2);
push @{ $myhash{$cols[0]} }, $cols[1];
@cols = split(" ", $line3);
push @{ $myhash{$cols[0]} }, $cols[1];
@cols = split(" ", $line4);
push @{ $myhash{$cols[0]} }, $cols[1];

foreach $k (sort {$a <=> $b} (keys %myhash)) {
   foreach $v(sort {$a <=> $b}(@{$myhash{$k}}))
   {
       print $k." : $v \n";
   }
}

Else, write the sort function for the second foreach to ignore the 'cols' word, and only use the second word for sorting.

Edit:

Well I wanted to evade writing that myself, but since you asked ;) this explains the gist:

foreach $k (sort {$a <=> $b} (keys %myhash)) {
   foreach $v(sort mysorter (@{$myhash{$k}})) #mysorter is a sub, defined further on
   {
       print $k." : $v \n";
   }
}

sub mysorter {
  my $c = $a;
  my $d = $b;

  $c =~ s/(.*) (.*)/\2/gi;
  $d =~ s/(.*) (.*)/\2/gi;

  return $c <=> $d;
}
Community
  • 1
  • 1
Konerak
  • 39,272
  • 12
  • 98
  • 118
  • I do need the string part as well. Actually this is a small test, in the real program I need to sort entire string lines by one column in them that happens to be numerical. But thanks - I think I know what to do now. – user961627 Oct 27 '11 at 11:20
  • Okay actually... having trouble doing that. What would be the correct way to ignore the "cols" word during sorting? – user961627 Oct 27 '11 at 13:19
  • @user961627 - easiest to write your own sorting function. I edited my answer to include a small example. More info in [perldoc sort](http://perldoc.perl.org/functions/sort.html) – Konerak Oct 27 '11 at 13:33