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

I have a hash of data on students. Each key is the student ID number. Each value contains the student's name and GPA separated by a tab character. For example:

$students{0354} = "Fred\t3.2"; $students{4873} = "Tom\t2.9"; $students{1522} = "Susan\t4.0";

I want to display all students sorted by the GPAs. I have the following code in place:

foreach $key(sort GPA_sort %students) { #etc }
but I don't know what to put in the GPA_sort subroutine.

What code should I put the GPA_sort subroutine?

Is it something like the following?

sub GPA_sort { $value_a = $students->{$a}; $value_b = $students->{$b}; ($name_a, $gpa_a) = split(/\t/, $value_a); ($name_b, $gpa_b) = split(/\t/, $value_b); $gpa_a <=> $gpa_b }

Any help would be gratefully appreciated. Thank you.

Replies are listed 'Best First'.
Re: sorting by field of a tab-delimited hash value
by bobf (Monsignor) on Jun 18, 2006 at 22:15 UTC

    You could certainly take that approach, but this might also be a good time to learn about the Schwartzian Transform. Using that technique will allow each element to be processed only once during the sort (which could be important for large lists of data that require expensive processing prior to sorting, but may not result in much of a time savings for your specific application). For example:

    foreach my $id ( map { $_->[1] } sort { $a->[0] <=> $b->[0] } map { [ (split( "\t", $students{$_} ))[1], $_ ] } keys %students ) { printf "$id\t$students{$id}\n"; }

    This prints:

    4873 Tom 2.9 236 Fred 3.2 1522 Susan 4.0
    Note that the leading zeros are left off of the student IDs. If you want them retained, printf will do the trick nicely: printf( "%04d\t$students{$id}\n", $id );

    Update: Since what is going on in the guts of the ST might be less than clear, here is a brief explanation (start reading from the second map and proceed upward):

    # Create a list of student IDs by taking the sorted # list of anonymous arrays and returning the second # element of each. map { $_->[1] } # Sort the list of anonymous arrays (created below) # numerically, using the first element in the array # (the GPA). sort { $a->[0] <=> $b->[0] } # For each key in %students, create an anonymous array: # The first element is the GPA, obtained by taking # the second element of the list created by splitting # the value of $students{$_} on the tab, and the second # element is the student ID. map { [ (split( "\t", $students{$_} ))[1], $_ ] } keys %students )
    The list of student IDs, now sorted by GPA, are what the foreach loop uses for printing. There are many other WTDI, of course. :-)

    HTH

Re: sorting by field of a tab-delimited hash value
by GrandFather (Saint) on Jun 18, 2006 at 22:23 UTC

    Invoking the "Schwartzian Transform" does the magic:

    use strict; use warnings; my %students; $students{0354} = "Fred\t3.2"; $students{4873} = "Tom\t2.9"; $students{1522} = "Susan\t4.0"; my @GPASorted = map {$_->[0]} sort { $a->[1] <=> $b->[1] } map {[$_, ($students{$_} =~ /\t(\d*(\.\d*)?)/)[0]]} keys %students; print "$students{$_}\n" for @GPASorted;

    Prints:

    Tom 2.9 Fred 3.2 Susan 4.0

    DWIM is Perl's answer to Gödel
Re: sorting by field of a tab-delimited hash value
by Zaxo (Archbishop) on Jun 19, 2006 at 02:16 UTC

    The suggestions you have are the way to go if you're stuck with the %students hash you've described. It would be more generally convenient, I think, to make that a hash of arrays (HoA). This will convert them in place:

    $_ = [split] for values %students;

    After Compline,
    Zaxo

      I agree that a hash of arrays would be convenient. However, the DB::File module in cpan does not allow hashes of arrays. It only allows hashes of strings.
Re: sorting by field of a tab-delimited hash value
by jwkrahn (Abbot) on Jun 18, 2006 at 22:15 UTC
    Something like that:
    sub GPA_sort { ( split /\t/, $students{ $a } )[ -1 ] <=> ( split /\t/, $students{ $b } )[ -1 ] }
      This looks great. But for some reason when I use this, the values of the hash are added to the hash as keys with no values. Any idea why??

      PS: I would really like to know why this is happening.

Re: sorting by field of a tab-delimited hash value
by TedPride (Priest) on Jun 19, 2006 at 03:46 UTC
    But what happens if the GPA is equal for a bunch of students and you want those students listed in alphabetic order? Your sort needs to be more advanced.
    use strict; use warnings; my %students = ( 0354 => "Fred\t3.2", 4873 => "Tom\t2.9", 4874 => "Bill\t2.9", 1522 => "Susan\t4.0", ); $_ = [split /\t/, $_] for values %students; print "$students{$_}[0] (#$_} $students{$_}[1]\n" for sort { $students{$b}[1] <=> $students{$a}[1] || $students{$a}[0] cmp $students{$b}[0] } keys %students;
Re: sorting by field of a tab-delimited hash value
by sh1tn (Priest) on Jun 19, 2006 at 09:01 UTC
    for my $key ( sort { ([split '\t', $students{$b}]->[1]) <=> ([split '\t', $students{$a}]->[1]) } keys %students) { print $students{$key}, $/ }


Re: sorting by field of a tab-delimited hash value
by salva (Canon) on Jun 20, 2006 at 10:00 UTC
    use Sort::Key qw(nkeysort); @sorted = nkeysort { (split /\t/, $students{$_})[1] } keys %students;