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

Dear Monks,

I have a key-value hash where all keys and values are codes relating to either apples, oranges or lemons. I want to find how many orange-apple, apple-lemon and lemon-orange pairs i have in the hash. All the codes relating to each fruit group are stored in seperate arrays.

However, I could really use some help on finding the best way to do this. Ideally I would like an output (preferably the same order as is in the hash) stating whether each key & value are apples, oranges or lemons:

e.g.

#e.g. lemon-lemon apple-orange orange-lemon orange-apple
Here is my code so far:
my %hash = map { $keys[$_] => $values[$_] } 0.. $#keys; for (my $i=0; $i<@apple_ids; $i++) { while ( my ($key, $value) = each(%hash) ) { if ($apple_ids[$i] == $key) { print "apple"; } if ($apple_ids[$i] == $value) { print "apple"; } } } for (my $i=0; $i<@orange_ids; $i++) { while ( my ($key, $value) = each(%hash) ) { if ($orange_ids[$i] == $key) { print "orange"; } if ($orange_ids[$i] == $value) { print "orange"; } } } for (my $i=0; $i<@lemon_ids; $i++) { while ( my ($key, $value) = each(%hash) ) { if ($lemon_ids[$i] == $key) { print "lemon"; } if ($lemon_ids[$i] == $value) { print "lemon"; } } }

Replies are listed 'Best First'.
Re: help querying a hash
by duff (Parson) on Apr 03, 2006 at 15:16 UTC

    That you want your output "in the same order as the hash" is a not a good sign. Hashes are unordered. If you want a specific order, then you'll need to specify it each time you access the hash.

    That said, here's what I think you want:

    my %fruit_map; @fruit_map{@apple_ids} = ("apple") x @apple_ids; @fruit_map{@orange_ids} = ("orange") x @orange_ids; @fruit_map{@lemon_ids} = ("lemon") x @lemon_ids; while (my($k,$v) = each %hash) { print "$fruit_map{$k}-$fruit_map{$v}\n"; }

    %fruit_map is just a hash that maps the ID to the appropriate fruit given that you have a list of IDs that belong to known fruit. I've used hash-slice syntax and the x operator in list context to set the hash in groups.

Re: help querying a hash
by jdporter (Paladin) on Apr 03, 2006 at 15:41 UTC

    duff is right on, wrt mapping each id to its fruit class.
    Now let's talk about the rest of the problem: checking all the input data, and printing the count summary.

    It appears that you don't actually start with a hash, but with two arrays, parallel - one for key, one for value. Converting that into a hash is a mistake, unless you don't care about counts. But you do. So let's just iterate over the input data directly:

    my( @keys, @values ); # wherever they come from. @keys == @values or die "input arrays not same length!"; my %fruit_map; # and populate as [duff] showed. my %pair_count; # accumulate results for my $i ( 0 .. $#keys ) { my $k = $fruit_map{ $keys[$i] }; my $v = $fruit_map{ $values[$i] }; $pair_count{"$k-$v"}++; } # now show results: for ( sort keys %pair_count ) { print "$_: $pair_count{$_}\n"; }

    You can achieve some degree of retaining the input order in the output by tie'ing %pair_count to Tie::IxHash.

    We're building the house of the future together.
Re: help querying a hash
by wazzuteke (Hermit) on Apr 03, 2006 at 19:39 UTC
    The comment 'Hashes are unsorted' is something to be aware of. The only way to sort a hash is via the sort perlfunc. However, I don't believe this is what you are looking for. I think you may be looking for a method of finding out what the count of all apples, oranges and lemons while the output will represent the order of which they were placed into the hash. To accomidate this, my example is to create an array reference of hash references that will allow for both. It may not be exactly what you are looking for, but it sounds very close. Therefore:
    #!/usr/bin/perl use strict; use warnings; # For example... my $fruits = []; my $apple = { 'apple' => 1 }; my $orange = { 'orange' => 1 }; my $lemon = { 'lemon' => 1 }; # First, something needs to populate the ref. # This is just an example, so you will naturally # have to modify this to fit your app better $fruits = [ $apple, $orange, $lemon, $orange ]; for my $fruit ( @$fruits ) { print "$_\n" for keys %$fruit; } exit( 0 );
    Albeit simplistic for what you are looking for, this may be closer to finding out what value of what is being placed into your 'hash'. Given that arrays are ordered, the array-ref wrapper is perhaps more appropriate to what you are looking for.

    I hope this helps in your journey!

    Good luck!

    ---hA||ta----
    print$_ for(map{chr($_)}split(/\s+/,join(/\B?:\w+[^\s+]/,<DATA>))); __DATA__ 67 111 100 101 32 80 101 114 108