jaydstein has asked for the wisdom of the Perl Monks concerning the following question:
I'm relatively new to perl and I'm at the point where I don't 100% understand the nitty gritty of what I am doing (but I'm trying ;) ). I'm not getting expected behavior from my code. I have a hash that contains either a scalar or an array, and I need to print, line-by-line, each value contained in a hash.
Here's my code:
my $primaryFeatures = {
'foo', ('fool', 'food', 'foot'),
'bar', ('barricade'),
};
while (my ($key, $value) = each(%$primaryFeatures)){
print "($key, $value)\n";
}
Here's what I want:
(foo, fool)
(foo, food)
(foo, foot)
(bar, barricade)
But one of the items in the array value ('fool', 'food', 'foot') is being picked up and interpreted as a key instead of a value. Here is what I am getting:
(foo, fool)
**(food, foot)**
(bar, barricade)
I've looked into the 'values' function. I could make that work... but sifting through the the keys and values for only the values is a little messy. What is the best way to do this?
Re: Hash value printing... WITH ARRAYS *dun dun dun*
by toolic (Bishop) on Feb 06, 2012 at 22:49 UTC
|
use warnings;
use strict;
my $primaryFeatures = {
'foo', ['fool', 'food', 'foot'],
'bar', ['barricade'],
};
while (my ($key, $value) = each(%$primaryFeatures)){
print "($key, $_)\n" for @{ $value };
}
__END__
(bar, barricade)
(foo, fool)
(foo, food)
(foo, foot)
I used square brackets to construct the arrays, then I dereferenced the arrays in the while loop.
It is also customary to use fat commas (=>) when constructing hashes
my $primaryFeatures = {
foo => ['fool', 'food', 'foot'],
bar => ['barricade'],
};
| [reply] [d/l] [select] |
Re: Hash value printing... WITH ARRAYS *dun dun dun*
by kcott (Archbishop) on Feb 06, 2012 at 23:46 UTC
|
The format of a hash is basically (key, value, key, value, etc.) so, despite adding the parentheses, what you are doing here is mapping foo to fool, food to foot and bar to barricade. An arrayref (reference to an array) is a single value and that's what you should be using here. So, your code becomes:
my $primaryFeatures = {
'foo', ['fool', 'food', 'foot'],
'bar', ['barricade'],
};
You'll need to dereference the arrayref (i.e. @$value) and print each element in some sort of loop. Simplistically, change the print statement to something like:
for my $element (@$value) {
print "($key, $element)\n";
}
That should get the code doing what you want. In addition, here's a few other suggestions:
- You can remove much of the punctuation-noise by using => (called the fat comma) and qw{} (quote-on-whitespace): foo => [qw{fool food foot}]
- Unless you really need a hashref, just use a hash: my %primaryFeatures = (...); and later each(%primaryFeatures)
- While there are many ways to do this, while and each are probably not the best choices here (I think you've probably guessed as much already) - for and keys would be better.
- Add use strict; and use warnings; to the top of your code.
Putting all that together, your code might look like:
use strict;
use warnings;
my %primaryFeatures = (
foo => [qw{fool food foot}],
bar => [qw{barricade}],
);
for my $key (keys %primaryFeatures) {
for my $element (@{$primaryFeatures{$key}}) {
print "($key, $element)\n";
}
}
each(), keys() and values() do not guarantee the order of data returned. If you want the exact order you've shown above, you'll need additional code. On my machine, this code outputs:
(bar, barricade)
(foo, fool)
(foo, food)
(foo, foot)
Finally, what I have here is not so much the correct answer but rather one of many possible working solutions. On this site and in Perl books and documentation you'll often come across the Perl motto: TMTOWTDI (There's more than one way to do it).
| [reply] [d/l] [select] |
Re: Hash value printing... WITH ARRAYS *dun dun dun*
by Anonymous Monk on Feb 06, 2012 at 22:56 UTC
|
Your data structure doesn't look like you think it does. Values can't be arrays. They have to be references.
Here's some similar code that may help you get going better. I also threw in a little use of Data::Dumper since it can be handy to figure out what what your data structures look like.
use warnings;
use strict;
use Data::Dumper;
my $primary_features = {
foo => [ 'fool', 'food', 'foot', ],
bar => [ 'barricade' ]
};
print Data::Dumper::Dumper( $primary_features );
while (my ($key, $value) = each(%$primary_features)){
print "($key, $value)\n";
}
Also, note a few things:
- I used => instead of ",". They're almost the same, except => puts whatever is to the left of it in quotes, and it's traditionally used to separate the keys and values of things in hash definitions.
- I made a reference to an array with square brackets instead of parentheses. You can make an array with the qw() operator, so qw( fool food fort ) is the same as 'fool', 'food', 'fort' , and may be easier to type.
- When you print out $value, it's going to show something like "ARRAY(0x10080fe18))" that means you're trying to print an array. You can iterate over your array or something like that to print something more useful.
- use warnings and strict. They help you find when you're typing something that doesn't make sense.
| [reply] [d/l] |
Re: Hash value printing... WITH ARRAYS *dun dun dun*
by mcdave (Beadle) on Feb 07, 2012 at 04:03 UTC
|
Lots of folks gave reasonable explanations of what the right answer might look like, but in case you're wondering what was wrong with the original: The "values" for foo and bar actually got flattened out into the list itself. So what you wrote was equivalent to
my $primaryFeatures = {
'foo', 'fool', 'food', 'foot',
'bar', 'barricade',
};
which is equivalent to
my $primaryFeatures = {
foo => 'fool',
food => 'foot',
bar => 'barricade',
};
which is why the (food, foot) pairing came out.
| [reply] [d/l] [select] |
Re: Hash value printing... WITH ARRAYS *dun dun dun*
by InfiniteSilence (Curate) on Feb 07, 2012 at 00:34 UTC
|
~linux> perl -MMath::Cartesian::Product -e 'my %hash = (q|foo|=>[qw|fo
+ol food foot|],q|bar|=>[qw|barricade|]); for (keys %hash) {cartesian
+{print qq|@_\n|} [$_],$hash{$_}};'
Produces
bar barricade
foo fool
foo food
foo foot
TIMTOWTDI
Celebrate Intellectual Diversity
| [reply] [d/l] [select] |
Re: Hash value printing... WITH ARRAYS *dun dun dun*
by JavaFan (Canon) on Feb 07, 2012 at 00:32 UTC
|
use 5.010;
my $hash = {
array_key => ['one', 'two', 'three'],
scalar_key => 'four',
};
while (my ($key, $value) = each %$hash) {
foreach my $element ("ARRAY" eq ref $value ? @$value : $value) {
say "($key, $element)"
}
}
| [reply] [d/l] |
|
|