Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

reusable hash value sort

by Anonymous Monk
on Jul 12, 2002 at 22:41 UTC ( [id://181414]=perlquestion: print w/replies, xml ) Need Help??

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

I did some looking and found much discussion of hash sorting but nothing that answered my specific question. I want to create a hash value sort funtion that can be reused by many hashes. Here's a simple version of what I have now:

foreach $key (sort byValue keys %myHash) { print "$key - $myHash{$key}\n"; } sub byValue {$myHash{$a} cmp $myHash{$b}};

What I want it a generic byValue funtion that does not contain the name of a specific hash (i.e. myHash). Any help is appreciated.

- Todd

Replies are listed 'Best First'.
Re: reusable hash value sort
by thelenm (Vicar) on Jul 12, 2002 at 22:56 UTC
    One option (maybe not the best) is to create a function that does the sort, rather than a function that does each comparison. For example:
    sub sorted_keys_by_value { my $hash = shift; return sort {$hash->{$a} cmp $hash->{$b}} keys %$hash; } @sorted_keys = sorted_keys_by_value(\%myHash);

    -- Mike

    --
    just,my${.02}

Re: reusable hash value sort
by DamnDirtyApe (Curate) on Jul 13, 2002 at 18:06 UTC

    Tie::SortHash may be what you want here.

    use Tie::SortHash; my %people = ( 'John Doe' => 33, 'Jane Doe' => 29, 'Jim Smith' => 15, ); my $sortblock = q( my $c = (split /\s+/, $a)[1]; my $d = (split /\s+/, $b)[1]; $c cmp $d || $hash{$a} <=> $hash{$b} ); tie %people, 'Tie::SortHash', \%people, $sortblock; foreach my $name ( keys %people ) { print $name . " is " . $people{$name} . " years old.\n"; } # This output will always be Jane Doe is 29 years old. John Doe is 33 years old. Jim Smith is 15 years old.

    _______________
    D a m n D i r t y A p e
    Home Node | Email
Re: reusable hash value sort
by Anonymous Monk on Jul 13, 2002 at 15:36 UTC
    i'm not really sure what you mean, but here goes:
    sub sortByValue { my %hash = @_; sort { $a->[1] cmp $b->[1] } map { [ $_, %hash{$_} ] } keys %hash; } foreach $pair (sortByBalue %myHash) { print "$pair->[0] - $pair->[1]\n"; }
    or, if sortByValue should return only the keys:
    sub sortByValue { my %hash = @_; sort { $hash{$a} cmp $hash{$b} } keys %hash; } foreach my $key (sortByValue %myHash) { print "$key - $myHash{$key}\n"; }
    the fun thing to do would be to tie a hash table to return it's keys sorted based on the values for the each function. (i'd bet my left pinky this exists already on CPAN)
Re: reusable hash value sort
by Courage (Parson) on Jul 13, 2002 at 07:04 UTC
    I can suggest a solution which is not good to use in programs, but meets your requirements.
    my $current_href = {}; sub byValue { $current_href->{$a} cmp $current_href->{$a}; } # ... somewhere in your program $current_href = \%myHash;

    Just assign hash ref to current hash in consideration

    Courage, the Cowardly Dog.

      Being a Perl newbie, I must ask,
      why is this "not good to use in programs"?
        I beleive it's better to create "independent" subroutines, in that sence of a word that they will not depend on a state of some external global variable. When you'll return to this subroutine after many years and will reuse it, then you have a chance to use it incorrectly.

        As a second point, you may accidentially break that external variable somehow and after that something bad will happen, including coredumps (AFAIK some versions of perl will coredump on sort routines when criteria subroutine does not behave well)

        Courage, the Cowardly Dog

Re: reusable hash value sort
by Revelation (Deacon) on Jul 13, 2002 at 16:04 UTC
    First of all, this task is not one that should be undertaken by a subroutine using sort(). One must remember that sort sorts into an array, so there is no knowledge of the hash we sorted from! Therefore, the task you propose is (probbably) impossiblie, as long as you want to sort by_something, without giving it the hashes name, and globalizing the hash, or just passing the hash itself (Which will come up later).

    The pragmatic programmer aproaches issues that he has trouble with, with differant. I have to ask, what is the purpose of a subroutine that sorts? Why not pass the subroutine a hash, , and hard code the sort/hash name into the script. (Does exactly the same thing, except you don't have to deal with those problems....)
    key_val(%whatever_hash_name_you_want_); sub key_val { my %hash = shift; map {print "$_ - $hash{$_} \n";} sort{$hash{$a} cmp $hash{$b}} keys %h +ash; }
    Passing coderefs would even allow you to print in any format you wished!

    If, for some reason you *have* to have a subroutine block in the sort: It would be more pragmatic to just use the hashname as an arg. You can then shift the hashname, and do the sort, but this would not compy with strict (you can figure the code out yourself :) )
Re: reusable hash value sort
by Anonymous Monk on Jul 14, 2002 at 02:13 UTC
    You could use a closure:
    use Carp; sub make_by_value_sorter { my ($hash, $op) = @_; return sub ($$) { $hash->{$_[0]} <=> $hash->{$_[1]} } if $op eq '<=>'; return sub ($$) { $hash->{$_[0]} cmp $hash->{$_[1]} } if $op eq 'cmp'; croak("Unknown sort op '$op'"); } my $hash = { bar => 1, foo => 2, baz => 3, fah => 4, }; my $by_value = make_by_value_sorter($hash, '<=>'); my @a = sort $by_value keys %$hash; # Simple case my @b = sort { substr($a, 0, 1) cmp substr($b, 0, 1) or $by_value->($a, $b) # As part of another sort sub } keys %$hash;
    Cheers,
    -Anomo
Re: reusable hash value sort
by jira0004 (Monk) on Jul 14, 2002 at 21:29 UTC
    Hi Todd,

    Stick your code that you've defined into a Perl subroutine that takes in a hash. Then you can call that subroutine each time you want to perform your algorithm. By the way, you don't need to define the byValue subroutine. Perl lets you use an anynomous subroutine right in the sort statement.

    The syntax of using the anynomous subroutine in the sort operation is:

    sort BLOCK LIST
    I took your code and put it into a subroutine that I called hash_value_sort and then I just called it on each hash instance I had. The subroutine just took in the hashes and worked with them. A Perl subroutine will take a list of scalars or a single hash. If you need to pass in something more complex -- multiple lists, multiple hashes, objects, etc. -- you can pass in references, a reference is just a scalar whose value is a reference. So you can pass in two lists or two hashes or whatever by passing in a reference to each thing. However, in your example you're only working with one thing and your not trying to modify it, so you don't need to pass in a reference, although if it were very big, you might want to since it would be much faster (in Perl a reference is like a pointer in C).

    Sticking the code you want to use over again into a subroutine that takes in a hash causes your hashes to get copies into that local hash in the subroutine you've written each time it's called. That local hash copy in the subroutine gets defined by using my, not by using local, which may seem confusing. Here's the code, hope it helps.

    #!perl use strict; &main(); sub main { my %hash1 = ( "george" => "salsa", "doug" => "apple", "steve" => "cantaloupe", "alex" => "blueberry", "andrew" => "ginger" ); my %hash2 = ( "william" => "cats", "robert" => "dogs", "randy" => "birds", "phillip" => "fish" ); my %hash3 = ( "amy" => "swims", "georgianne" => "runs", "susan" => "jogs", "stephanie" => "walks", "sarah" => "bicycles", "debbie" => "dances" ); print "The result of calling hash_value_sort on \%hash1 is\n"; &hash_value_sort( %hash1 ); print "\n"; print "The result of calling hash_value_sort on \%hash2 is\n"; &hash_value_sort( %hash2 ); print "\n"; print "The result of calling hash_value_sort on \%hash3 is\n"; &hash_value_sort( %hash3 ); print "\n"; my @keys_sorted_by_value1 = &get_sorted_keys( %hash1 ); my @keys_sorted_by_value2 = &get_sorted_keys( %hash2 ); my @keys_sorted_by_value3 = &get_sorted_keys( %hash3 ); print ( "The keys of \%hash1 sorted by their values are: ", ( join +( ", ", @keys_sorted_by_value1 ) ), "\n" ); print ( "The keys of \%hash2 sorted by their values are: ", ( join +( ", ", @keys_sorted_by_value2 ) ), "\n" ); print ( "The keys of \%hash3 sorted by their values are: ", ( join +( ", ", @keys_sorted_by_value3 ) ), "\n" ); print "\n"; return; } sub hash_value_sort { my %h = ( @_ ); my $key; foreach $key ( sort { $h{$a} cmp $h{$b} } keys %h ) { print "$key - $h{$key}\n"; } return; } sub get_sorted_keys { my %h = ( @_ ); return sort { $h{$a} cmp $h{$b} } keys %h; }
Re: reusable hash value sort
by Anonymous Monk on Jul 13, 2002 at 21:16 UTC
    use strict; # never forget ;-)
    foreach my $key (sort values %hash) { .... }
      This won't give you the hash keys sorted by value (what the original poster wanted)... it will give you a sorted list of the hash values. The variable name $key is misleading, and should probably be changed to $value.

      -- Mike

      --
      just,my${.02}

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://181414]
Approved by BrowserUk
Front-paged by wil
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others wandering the Monastery: (6)
As of 2024-04-18 18:05 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found