CGI/Perl Guide | Learning Center | Forums | Advertise | Login
Site Search: in

  Main Index MAIN
INDEX
Search Posts SEARCH
POSTS
Who's Online WHO'S
ONLINE
Log in LOG
IN

Home: Perl Programming Help: Intermediate:
sorting by element of an array within a hash

 



lilmark
New User

Mar 1, 2013, 9:05 AM

Post #1 of 6 (385 views)
sorting by element of an array within a hash Can't Post

 I'm having trouble sorting by the PERCT1 element in descending order. What I attempted to do below was...

-store the month as the key for %MMT and the rest of the data as the value

-store the month as the key for %MMTsort and PERCT1 as the value

-this will create both Hashes in the same order

-then sort %MMTsort by its value/PERCT1 in descending order and use the returned location/index of the element to print out %MMT in the proper order



But it's not working. It's still printing it out in the same order it was store into the hash. Anyone see what I am overlooking?



open FP, "$fn1" || die "Cannot open $fn1\n";
while (chomp($r=<FP>))
{
$r=~s/\r//;
@arr=split /\t/, $r;
next if $arr[1] eq "Total";
@arr2=($arr[1], $arr[2], $arr[3], $arr[4], $arr[5]);
push @{ $MMT{$arr[0]} }, [@arr2];
push @{ $MMTsort{$arr[0]} }, $arr[2];
}
close FP;
foreach $yrmo (sort keys %MMT)
{
@b=(0..$#{$MMTsort{$yrmo}});
print "yrmo=$yrmo\n";
foreach $index (sort byMOM @b)
{
printf "%s\n", join "\t", @{$MMT{$yrmo}[$index]};
}
print "\n";
}



MONTH GROUPS PERCT1 NMBR CNT PERCT2
201302 GROUP_1 0.44 201 6,000 0.91
201302 GROUP_2 0.42 192 10,806 1.65
201302 GROUP_3 0.38 159 4,065 0.62
201303 GROUP_11 0.15 -474 4,691 0.58
201303 GROUP_15 0.08 -209 3,010 0.37
201303 GROUP_13 0.05 -93 9,879 1.22
201304 GROUP_12 0.04 184 10,977 2.13
201304 GROUP_4 0.00 91 9,184 1.78
201304 GROUP_5 -0.01 87 14,042 2.72
201305 GROUP_15 0.35 47 3,667 0.71
201305 GROUP_10 0.34 44 8,961 1.74
201305 GROUP_7 0.30 26 8,130 1.58




I managed to make it work using the below code of arrays, but I'm assuming I'm not doing something wrong when involving the hash.

my $c=1;
@{$a[1]}=(2,7,5,4,6,9,8,1,3);

@b=(0..$#{$a[1]});

sub byMOM{
$a[$c][$b]<=>$a[$c][$a];}


print "@b\n";
0 1 2 3 4 5 6 7 8
printf "%s\n", join "\n", sort byMOM @b;
5
6
1
4
2
3
8
0


(This post was edited by lilmark on Mar 1, 2013, 9:09 AM)


BillKSmith
Veteran

Mar 1, 2013, 12:43 PM

Post #2 of 6 (376 views)
Re: [lilmark] sorting by element of an array within a hash [In reply to] Can't Post

I suspect that your error is in the real byMOM function which you did not post. Please provide this function and some sample data so we can try your code.

Your algorithm seems far to complicated. The field that you want to sort on is already in your %MMT hash. Use a Schwartzian Transform to sort on this field. Refer to the perl FAQ perldoc -q "How do I sort an array"
Good Luck,
Bill


Laurent_R
Enthusiast / Moderator

Mar 1, 2013, 2:02 PM

Post #3 of 6 (372 views)
Re: [lilmark] sorting by element of an array within a hash [In reply to] Can't Post

I agree with BillKSmith that your algorithm is far too complicated for what you want to do. And, if I understood correctly what you are trying to do, it is also wrong, because you seem to rely on the order in which items were stored into the hash. There is absolutely no predictable relation between the order in which values are stored in a hash and the order in which you'll retrieve them. This is presumably why your algorithm seems to work with arrays (the insertion order is preserved) and not with hashes (the order is not preserved).

Having said that, what you are trying to do is not very clear (at least not to me). Please provide a sample of the data you want to sort and state clearly on which fields or keys you want it to be sorted. As Bill said, a Schwartzian Transform is likely to be a good method, but we need a netter understanding of your requirement before we can say more on that.


Kenosis
User

Mar 1, 2013, 7:34 PM

Post #4 of 6 (365 views)
Re: [lilmark] sorting by element of an array within a hash [In reply to] Can't Post

It appears that you're attempting to sort your records based upon the value of PERCT1. A Schwartzian Transform (ST) is the canonical way of sorting records based upon a column's values. However, if each of your records is uniquely identified (and it appears that, at least, Group_n may do this), then you can use a hash where the key is the record, the value is that record's PERCT1, and you sort the hash by value:


Code
use strict; 
use warnings;

my %hash;
do{chomp; $hash{$_} = (split)[2]} for <DATA>;
print "$_\n" for sort { $hash{$b} <=> $hash{$a} } keys %hash;

__DATA__
201302 GROUP_1 0.44 201 6,000 0.91
201302 GROUP_2 0.42 192 10,806 1.65
201302 GROUP_3 0.38 159 4,065 0.62
201303 GROUP_11 0.15 -474 4,691 0.58
201303 GROUP_15 0.08 -209 3,010 0.37
201303 GROUP_13 0.05 -93 9,879 1.22
201304 GROUP_12 0.04 184 10,977 2.13
201304 GROUP_4 0.00 91 9,184 1.78
201304 GROUP_5 -0.01 87 14,042 2.72
201305 GROUP_15 0.35 47 3,667 0.71
201305 GROUP_10 0.34 44 8,961 1.74
201305 GROUP_7 0.30 26 8,130 1.58

Output:


Code
201302 GROUP_1 0.44 201 6,000 0.91 
201302 GROUP_2 0.42 192 10,806 1.65
201302 GROUP_3 0.38 159 4,065 0.62
201305 GROUP_15 0.35 47 3,667 0.71
201305 GROUP_10 0.34 44 8,961 1.74
201305 GROUP_7 0.30 26 8,130 1.58
201303 GROUP_11 0.15 -474 4,691 0.58
201303 GROUP_15 0.08 -209 3,010 0.37
201303 GROUP_13 0.05 -93 9,879 1.22
201304 GROUP_12 0.04 184 10,977 2.13
201304 GROUP_4 0.00 91 9,184 1.78
201304 GROUP_5 -0.01 87 14,042 2.72

However, if it's possible for two keys to be the same, use the ST:


Code
print map "$_->[0]\n", 
sort { $b->[1] <=> $a->[1] }
map {chomp; [ $_, (split)[2] ] }
<DATA>;

To my eyes, the code for sorting by value appears simpler, and it benchmarks modestly faster than the ST on your small data set:


Code
use strict; 
use warnings;
use Benchmark qw(cmpthese);

chomp ( my @data = <DATA> );

# Schwartzian Transform
sub stSort {
my @sorted = map $_->[0],
sort { $b->[1] <=> $a->[1] }
map { [ $_, (split)[2] ] }
@data;
}

# Sort by hash value
sub valSort {
my %hash;
$hash{$_} = (split)[2] for @data;
my @sorted = sort { $hash{$b} <=> $hash{$a} } keys %hash;
}

cmpthese(
-5,
{
stSort => sub { stSort() },
valSort => sub { valSort() }
}
);

__DATA__
201302 GROUP_1 0.44 201 6,000 0.91
201302 GROUP_2 0.42 192 10,806 1.65
201302 GROUP_3 0.38 159 4,065 0.62
201303 GROUP_11 0.15 -474 4,691 0.58
201303 GROUP_15 0.08 -209 3,010 0.37
201303 GROUP_13 0.05 -93 9,879 1.22
201304 GROUP_12 0.04 184 10,977 2.13
201304 GROUP_4 0.00 91 9,184 1.78
201304 GROUP_5 -0.01 87 14,042 2.72
201305 GROUP_15 0.35 47 3,667 0.71
201305 GROUP_10 0.34 44 8,961 1.74
201305 GROUP_7 0.30 26 8,130 1.58

Results:


Code
           Rate  stSort valSort 
Rate stSort valSort
stSort 47282/s -- -12%
valSort 54007/s 14% --



BillKSmith
Veteran

Mar 1, 2013, 9:27 PM

Post #5 of 6 (360 views)
Re: [Laurent_R] sorting by element of an array within a hash [In reply to] Can't Post

I believe that I understand the problem. The OP is not trying to "sort a hash".
The key of the hash %MMT is a timestamp ($yrmo). All records having that timestamp
are stored as an array of arrays. The first array is a list of references to
the records. Each record is stored as an array of fields.
The objective is (for each time stamp) to sort the records into an order
specified by the third field in the original record. The second hash (%MMTsort)
was created in the mistaken belief that it would help solve the sorting problem.

The following code performs the sort I described above.




Code
@sorted_records =  
map {$_->[1]}
sort {$b->[0] <=> $a->[0]}
map {[$_->[1], $_]} @{$MMT{$yrmo}};


Perhaps the real intent is to sort records first by time and then by PERCT1. In that case, the hash is the wrong data structure.
Good Luck,
Bill


Kenosis
User

Mar 1, 2013, 10:52 PM

Post #6 of 6 (355 views)
Re: [BillKSmith] sorting by element of an array within a hash [In reply to] Can't Post

Excellent observations, and saying that Perhaps the real intent is to sort records first by time and then by PERCT1. makes sense. If this is the intent, then your original ST suggestion is well suited for the task. Perhaps something like (plugged into my examples above):


Code
print map "$_->[0]\n", 
sort { $a->[1] <=> $b->[1] || $b->[2] <=> $a->[2] }
map { chomp; [ $_, (split)[0, 2] ] }
<DATA>;


 
 


Search for (options) Powered by Gossamer Forum v.1.2.0

Web Applications & Managed Hosting Powered by Gossamer Threads
Visit our Mailing List Archives