tags:

views:

221

answers:

3

I've got a data structure that is a hash that contains an array of hashes. I'd like to reach in there and pull out the first hash that matches a value I'm looking for. I tried this:

   my $result = shift grep {$_->{name} eq 'foo'} @{$hash_ref->{list}};

But that gives me this error: Type of arg 1 to shift must be array (not grep iterator). I've re-read the perldoc for grep and I think what I'm doing makes sense. grep returns a list, right? Is it in the wrong context?

I'll use a temporary variable for now, but I'd like to figure out why this doesn't work.

+10  A: 

A list isn't an array.

my ($result) = grep {$_->{name} eq 'foo'} @{$hash_ref->{list}};

… should do the job though. Take the return from grep in list context, but don't assign any of the values other than the first.

David Dorward
I think I'll go write `A list isn't an array` 100 times on my whiteboard. Thanks.
wes
There's a much better answer for that FAQ now: http://www.effectiveperlprogramming.com/blog/39
brian d foy
@brian d foy — When does the FAQ get updated? (i.e. will we see it in a point release of Perl 5 version 10, or will it be in version 12?)
David Dorward
It gets updated with every version of Perl.
brian d foy
@brian d foy -- From your description it sounds like the distinction between Perl lists and arrays is analogous to the Python distinction between tuples (immutable indexed collections) versus lists (mutable indexed collections).
Jim Dennis
Even better would be to simply: `use List::Util 'first';` as Leon said -- why keep searching for matches when you only need the first one?
Ether
+13  A: 

I think a better way to write this would be this:

use List::Util qw/first/;

my $result = first { $_->{name} eq 'foo' } @{ $hash_ref->{list} };

Not only will it be more clear what you're trying to do, it will also be faster because it will stop grepping your array once it has found the matching element.

Leon Timmermans
+1  A: 

Another way to do it:

my $result = (grep {$_->{name} eq 'foo'} @{$hash_ref->{list}})[0];

Note that the curlies around the first argument to grep are redundant in this case, so you can avoid block setup and teardown costs with

my $result = (grep $_->{name} eq 'foo', @{$hash_ref->{list}})[0];

“List value constructors” in perldata documents subscripting of lists:

A list value may also be subscripted like a normal array. You must put the list in parentheses to avoid ambiguity. For example:

# Stat returns list value.
$time = (stat($file))[8];

# SYNTAX ERROR HERE.
$time = stat($file)[8];  # OOPS, FORGOT PARENTHESES

# Find a hex digit.
$hexdigit = ('a','b','c','d','e','f')[$digit-10];

# A "reverse comma operator".
return (pop(@foo),pop(@foo))[0];

As I recall, we got this feature when Randal Schwartz jokingly suggested it, and Chip Salzenberg—who was a patching machine in those days—implemented it that evening.

Update: A bit of searching shows the feature I had in mind was $coderef->(@args). The commit message even logs the conversation!

Greg Bacon