Join 3,439 readers in helping fund MetaFilter (Hide)


DWIM means do what I mean, dammit!
March 26, 2009 10:00 PM   Subscribe

Perlfilter: I'm having problems iterating over and testing for keys within a hash of hashes.

Example code (apologies for the poor layout):

use strict;

my %h = ( apples => {
grannysmith => 5,
reddelicious => 3
},
oranges => 2,
pears => 3 );

print "$_\n" for keys %h;

# problem 1
print "$_\n" for keys %{%h->{apples}}; # Warning: Using a hash as a reference is deprecated.

# workaround
my $h_ref = \%h;
print "$_\n" for keys %{$h_ref->{apples}};

# problem 2
for my $fruit ( keys %{$h_ref->{apples}} ) {
if ( exists $fruit->{reddelicious} ) { # Error: Can't use string ("grannysmith") as a HASH ref while "strict refs" in use at test.pl line xx
print "Found red delicious key\n";
}
}


The code flagged as problem 1 returns
grannysmith
reddelicious

but with a warning: "Using a hash as a reference is deprecated." What would the undeprecated form be? I can make it work without the warning using the code flagged with workaround, but I don't see why I should have to create an extra reference if I can avoid it.

The code at problem 2 produces a flat-out error: "Can't use string ("grannysmith") as a HASH ref while "strict refs" in use at test.pl line xx". What it should do (what I want it to do) is print "Found red delicious key". What am I doing wrong?
posted by Ritchie to Computers & Internet (12 answers total) 4 users marked this as a favorite
 
Does this work?
print "$_\n" for keys %{ $h{apples} }

posted by suedehead at 10:06 PM on March 26, 2009


Er, also, for problem 2, $fruit will be a string for each iteration of the loop, since 'keys' will return a string for each key of the hash ---- yet you're treating it like it'll be a hash reference.

I'm not sure what you're trying to do but I'm assuming you're trying to iterate within the keys of the hash-within-the-hash (within the apples hash) and am trying to find whether or not 'reddelicious' exists?

Then do either:
for my $fruit ( keys %{$h{apples}} ) {
if ($fruit eq "reddelicious" ) { print "Found red delicious key\n";}
}
or
if (exists $h{apples}{reddelicious} ) {
print "Found red delicious key\n";
}

posted by suedehead at 10:18 PM on March 26, 2009


What I might do, using Perl's unique autovivification feature:

#!/usr/bin/perl -w

use strict;
use warnings;

my $h;

$h->{apples}->{grannysmith} = 5;
$h->{apples}->{reddelicious} = 5;
$h->{oranges} = 2;
$h->{pears} = 5;

foreach my $fruitName (keys %{$h}) {
 print "Fruit: $fruitName\n";
}

foreach my $appleName (keys %{$h->{apples}}) {
 print "Apple name: $appleName\n";
}

foreach my $fruitName (keys %{$h}) {
 if ($fruitName =~ /apples/) {
  if (defined $h->{$fruitName}->{reddelicious}) {
   print "found red delicious apple\n";
  }
 }
}

exit 0;

posted by Blazecock Pileon at 11:34 PM on March 26, 2009


First:
print "$_\n" for keys %{%h->{apples}}; # Warning: Using a hash as a reference is deprecated.
Classic new person mistake :) See the "%h" I put in italics? You want "$h" instead. Reason: the signul -- @, %, $, or sometimes * -- in front determines what type of data you want out of the evaluation of the expression. You want the scalar value out, so you need to use $. Consider these examples:
%h = (a => 'apple', b => 'banana', c => 'corn');
%h; # evaluates to the list ('a', 'apple', 'b', 'banana', 'c', 'corn')
    # although not specifically in that order
@h{'a', 'b'}; # evaluates to the list ('apple', 'banana')
$h{'a'}; # evaluates to the scalar 'apple'
Note how I used $h{'a'} and not $h->{'a'}. That's because h is a hash, not a reference to a hash. You made that mistake too.

Second:
for my $fruit ( keys %{$h_ref->{apples}} ) { if ( exists $fruit-&gt{reddelicious} ) { print "Found red delicious key\n"; } }
Think for a minute about what $fruit is. It's your hash key, which is a scalar string. It makes no sense to dereference a scalar string as a hash, which is exactly what the error told you. It's the same as writing "reddelicious"->{reddelicious}. I don't know what you're trying to do with this bit of code. If you want to see if there exists 'reddelicious" key in 'apples', then this suffices:
if (exists $h{'apples'}->{'reddelicious'}) { ... }
Note the indirection operator on the SECOND part. That's because your first part is a HASH, which contains a HASH REFERENCE. If you want to find what fruit has a key 'reddelicious', then you do this:
for my $fruit ( keys %h ) { if ( ref( $h{$fruit} ) eq 'HASH') && exists $h{$fruit}-&gt{reddelicious} ) { print "Found red delicious key\n"; } }
You need to check with ref() the type of data in each key since your hash %h mixes scalars and hash references.
posted by sbutler at 12:12 AM on March 27, 2009


References sound scary in Perl, but they get much easier to work with the more you use them with indirection arrow operations. A lot less guesswork and cleaner code.
posted by Blazecock Pileon at 12:15 AM on March 27, 2009


Let me say just a couple more things about perl variables that may help you here.

#1: Consider this:
my $foo = 1234;
my @foo = ('bar', 'baz');
my %foo = (one => 1, two => 2, three => 3, four => 4);

printf "%s\n", $foo;
printf "%s\n", $foo[ 0 ];
printf "%s\n", $foo{ 'three' };
What is the output? Better question is, how does perl tell the difference between $foo, @foo, and %foo in those print statements?!?

The answer is not the signul out front! Noticed I used the same one for all three prints because in all three cases I wanted a scalar out of the expression. The way perl knows the difference is by how the variable is used!

It knows the first $foo evaluates to 1234 because it is just plain.

It knows the second $foo[ 0 ] evaluates to 'bar' because it has array index brackets after it.

It knows the third $foo{ 'three' } evaluates to 3 because it has the hash key lookup braces after it.

#2: There is no way to express an immediate hash in perl. What do I mean by immediate? I mean an expression that all by itself, ignoring constants, evaluates to a hash. Your basic types are:
# types of scalars
1234;
'this is a string';

# types of lists
(1, 2, 3, 4);
('foo', 'bar', 'baz');
(a => 'one', b => 'two', c => 'three');
('a', 'one', 'b', 'two', 'c', 'three');
No hashes! You might be tempted to say that list #3 is a hash, but it's not. It's just a fancy way of writing exactly the same list #4. That is, '=>' in perl is almost exactly like ',' (except it always interprets its left hand side as a string, even when not quoted).

There are also some types of scalars you run into all the time:
# scalar reference
\1234;
\'this is a string';

# list reference
[ 1, 2, 3, 4 ];
['foo', 'bar', 'baz'];
[a => 'one', b => 'two', c => 'three'];
['a', 'one', 'b', 'two', 'c', 'three'];
\@foo;

# hash reference
{a => 'one', b => 'two', c => 'three'};
{'a', 'one', 'b', 'two', 'c', 'three'};
\%foo;

# subroutine reference
sub { print "test\n"; }
\&foo;
But again, those are all scalars. They just happen to be references to data of another type. And when you have a scalar that is a reference, that's when the indirection operator comes in, or sometimes you have two signuls together:
$$scalarref; ${$scalarref};
@$listref; @{$listref};
%$hashref; %{$hashref};
&$subref;

posted by sbutler at 12:38 AM on March 27, 2009


Usually you'll get a better response if you ask this type of question on Perlmonks or on Stack Overflow.

Meanwhile here's a diff that solves your problem for you. I suggest that you read perldoc perlreftut a couple of times as you're still confused about perl references. Also the book Perl Best Practices is brilliant for working out the right way to deal with references. I can tell that you're still confused rather than badly informed because it took me ages (5-10 minutes) to work out what you'd done wrong.

Also good to see you're using perl. Stick with it, it reaps rewards (and for object orientated stuff, ignore all other advice and use Moose;)
--- doit_orig.pl	2009-03-27 19:09:03.000000000 +1100
+++ doit.pl	2009-03-27 19:25:04.000000000 +1100
@@ -1,3 +1,5 @@
+#!/usr/bin env perl
+use warnings;
 use strict;
 
 my %h = ( apples => {
@@ -10,15 +12,16 @@
 print "$_\n" for keys %h;
 
 # problem 1
-print "$_\n" for keys %{%h->{apples}}; # Warning: Using a hash as a reference is deprecated.
+print "$_\n" for keys %{ $h{apples} }; # fixed!
 
 # workaround
 my $h_ref = \%h;
 print "$_\n" for keys %{$h_ref->{apples}};
 
 # problem 2
+$DB::single=1;
 for my $fruit ( keys %{$h_ref->{apples}} ) {
-if ( exists $fruit->{reddelicious} ) { # Error: Can't use string ("grannysmith") as a HASH ref while "strict refs" in use at test.pl line xx
+if ( $fruit eq 'reddelicioius' ) { # no warning now!
 print "Found red delicious key\n";
 }
 }

posted by singingfish at 1:30 AM on March 27, 2009


also you should indent properly. Perl Best Practices is worth looking at for advice here too.
posted by singingfish at 1:31 AM on March 27, 2009


I'm a bit thick when it comes to data structures, so whenever I have trouble identifying structures, I just use Data::Dumper::Simple and Dumper the recalcitrant structure to stdout. Enlightenment follows (usually).
posted by scruss at 3:00 AM on March 27, 2009 [1 favorite]


First, if all you're trying to do is print this out, use Data::Dumper or a related module.

The larger problem seems to be that you're dealing with a data structure that mixes string scalars and references to hashes, and you're not sure how to deal with it. I do this often, and provided an example here.
use strict;
use warnings; # always use this

my %h = (
  apples => {
    grannysmith => 5,
    reddelicious => 3
  },
  oranges => 2,
  pears => 3 );

for my $fruit ( keys %h )
{
  if ( ref $h{$key} eq "" )
  {
    print $h{$fruit}, " ", $fruit, "\n";
  }
  elsif ( ref $h{$key} eq "HASH" )
  {
    for my $type ( keys %{$h{$key}} )
    {
      print $type, " ", $fruit, " ", $h{$fruit}->{$type}, "\n";
    }
  }
  else
  {
    print STDERR ref $h{$key}, " references not permitted\n";
    exit( -1 );
  }
}

posted by mohrr at 3:59 AM on March 27, 2009


Personaly I like the debugger for working out where I am in a data strucutre, hence why I had $DB:single=1 (force a breakpoint running under perl -d) in my diff. You can use "x $data" to get a nice readable dump of the data structure.
posted by singingfish at 2:51 PM on March 27, 2009


Gah! It was all so obvious (in retrospect).

I'm coming back to perl after a long hiatus in which I've been working with ksh and sql scripts, so I'm rusty -- and I was never never much more than a perl beginner to start with.

I posted this question on a Friday afternoon after I'd spent most of the day wrestling with an issue I thought I understood. I'd re-read perlretut and flipped back and forth through the camel book to no avail. I'd searched perlmonks but couldn't seem to fix on the answer.

In the end I was doing the stupidest thing possible - randomly swapping around sigils, braces, and indirection operators in a half-assed attempt at making something happen. What was lacking was a clearer understanding, and I your answers have assisted greatly. Also it's Monday, and I've had coffee.

For those interested, I'm writing a script that builds special types of script for our BI server and then executes them - under Windows. It reads in a config file (using Config::General) to determine what needs to be executed (and when, and how), and then builds the script and runs it at the appointed interval or after the nominated event occurs.

It's a biggish project that goes a bit beyond what I currently know of perl (as you've already seen), but I figure the best way to learn something new is to bite off more than I can chew and then chew as fast as possible.

I've already used Data::Dumper to make sure that my config file loads up properly and everything is where I thought it would be. Config::General parses your config file into a hash (not a hashref). The internal structure of the hash depends entirely on what was in your config, but in my case contains ordinary scalar values, references to hashes, and references to arrays.

Someone referenced Perl Best Practices - I have read parts of that book, and usually my code is fairly cleanly laid out (to my eyes at least). I just didn't know how to get the indenting working within the <code> tag in my post. When I was at Monash I snuck into a few of Damian Conway's lectures. He's quite an amazing guy.

Thanks again to everyone who replied. You are a pack of magnificent bastards.
posted by Ritchie at 4:33 PM on March 29, 2009


« Older Where can I find the names of ...   |  You have Crohn's disease. You... Newer »
This thread is closed to new comments.