The techniques and patterns covered in the other responses here are tried and true idioms that are essential for getting the most out of Perl, for understanding existing code, and for working with the large installed base of older perl compilers. Just for fun I thought I mention a couple of other approaches:
There's a fairly readable new syntax in perl-5.22
that is an alternative to the more classic approach take by @fugu. For something a bit more funky I'll mention @miyagawa's Hash::MultiValue
. Perl 6 also has a nice way to convert lists of key/value pairs with potentially non-unique keys into hashes containing keys with multiple values.
As the other responses here point out, the "key" to all of this is:
For a hash key to refer to multiple values, the values need to be not just a list or array but a anonymous array [ ]
or a reference.
Using new syntax available with perl-5.22
Fugu's response shows the standard Perl idiom. Iterating through @names
using for 0 .. $#names
ensures that overlapping keys are not "lost" and instead point at an anonymous array of multiple values. With perl-5.22
we can use the pairs()
function from List::Util
(a core module) and postfix dereferencing to add key/value pairs to a hash and account for overlapping or duplicate keys in a slightly different way:
use experimental qw(postderef);
use List::Util qw/pairs/;
my %hash;
my $a_ref = [ qw/tinku 15 tina 4 rita 18 tinku 18 tinku 17 tinku 16 rita 19/ ];
push $hash{$_->key}->@* , $_->value for pairs @$a_ref;
use DDP;
p %hash;
As of version 1.39 List::Util::pairs()
returns ARRAY references as blessed objects accessible via ->key
and ->value
methods. The example uses LEONT's experimental.pm
pragma and DDP
to make things a bit more compact.
Output:
{
rita [
[0] 18,
[1] 19
],
tina [
[0] 4
],
tinku [
[0] 15,
[1] 18,
[2] 17,
[3] 16
]
}
As to which is more "readable": it's hard to beat the easily "grokable" standard approach, but with the new syntax available in the latest versions of perl5 we can explore the potential of new idioms. I am really starting to like postfix dereferencing. TIMTOWTDI and beyond!
@miyagawa's Hash::MultiValue
With this module all you can create a Hash::MultiValue
object (with lots of methods to access it in various ways) and a plain hash reference to conveniently work with multiple values per key.
#!/usr/bin/env perl -l
use Hash::MultiValue;
use strict;
use warnings;
my $mvhash = Hash::MultiValue->new(tinku =>15, tina =>4, rita =>18,
tinku =>18, tinku =>17, tinku =>16, rita =>19);
print "\ntinku's values:\n", join " ", $mvhash->get_all('tinku');
print "\nflattened mvhash:\n", join " ", $mvhash->flatten ;
print "\n ... using mvhash as a hashref:" ;
print join " ", $mvhash->get_all($_) for keys %$mvhash ;
print "\n", '... as a "mixed" hashref with each():';
my $mvhash_ref = $mvhash->mixed ;
while ( my ($k, $v) = each $mvhash_ref ) {
print "$k => " , ref $v eq "ARRAY" ? "@{$v}" : "$v" ;
}
Output:
tinku's values:
15 18 17 16
flattened mvhash:
tinku 15 tina 4 rita 18 tinku 18 tinku 17 tinku 16 rita 19
... using mvhash as a hashref:
15 18 17 16
18 19
4
... as a "mixed" hashref with each():
tinku => 15 18 17 16
rita => 18 19
tina => 4
Once your hash is available as a Hash::MultiValue
object you can manipulate it in various ways to quickly create temporary copies and hash references. Just assign them to a scalar and Dump
the (or use DDP
) to get an idea of how it works:
use DDP;
my $hmulti = $mvhash->multi; p $hmulti ;
my $hmixed = $mvhash->mixed; p $hmixed
There's some restrictions on using regular hash operations with a Hash::MultiValue
object (and things like dd \$mvhash
are not going to show you the whole hash - you need to do dd $hash->multi
) however in some situations there is an advantage to working with multi-value hashes in this way (i.e. more readable and/or possibly less code needed for some functions).
You still need to recognize when/where Hash::MultiValue
is useful so it's not unambiguously "easier" or "cleaner" - but it's another useful addition to your box of perl tools.
Perl 6 - just for comparison
Perl6 can be a bit more compact for grabbing key/value pairs from a list because you can use "multiple parameters" in a for
statement, traversing a list by groups of elements then using push
to arrange them into a hash. You can do this in a way that "automagically" accounts for overlapping keys. cf. this short perl6 snippet:
my %h ;
for <tinku 15 tina 4 rita 18 tinku 18 tinku 17 tinku 16 rita 19> -> $k, $v {
%h.push($k => $v) ;
}
%h.perl.say ;
Edit:
The friendly folks on #perl6
suggest an even more succinct "method":
my %h.push: <tinku 15 tina 4 rita 18 tinku 18 tinku 17 tinku 16 rita 19>.pairup ;
%h.perl.say ;
Output:
{:rita(["18", "19"]), :tina("4"), :tinku(["15", "18", "17", "16"])}<>
cf.
It's not just continued development of perl
the compiler that makes it possible to write Perl code in new and interesting ways. Thanks to @miygawa and Paul Evans for his stewardship of Scalar-List-Utils you can do cool things with Hash::MultiValue
even if your version of perl
is as old as version 5.8; and you can try the functions available in latest versions of List::Util
even if your perl
is barely from this millennium (List::Util
works with perl-5.6
which ushered in the 21st century in March 2000).