tags:

views:

1466

answers:

6

The following snippet searches for the index of the first occurrence of a value in an array. However, when the parentheses around $index are removed, it does not function correctly. What am I doing wrong?

my ($index) = grep { $array[$_] eq $search_for } 0..$#array;
+21  A: 

The parentheses change the context in which the grep is evaluated from scalar context to list context. In scalar context grep returns the number of times the expression was true. In list context it returns the elements for which the expression was true.

The following highlights the difference context makes:

my   $x   = grep {/foo/} @array;  # the number of things that match /foo/
my  ($x)  = grep {/foo/} @array;  # the first thing that matches /foo/
my  @foo  = grep {/foo/} @array;  # all the things that match /foo/
my (@foo) = grep {/foo/} @array;  # all the things that match /foo/
Michael Carman
`scalar @array` is number of elements in `@array`
Jakub Narębski
+1  A: 

grep returns a list. When you put the scalar variable name inside parentheses, Perl treats that whole l-value as a list, so it assigns the first value in the list to that variable.

If you had other scalars in the parentheses, you'd get the second, third, etc values from grep's return array in them:

my ($index, $foo, $bar) = grep { $array[$_] eq $search_for } 0..$#array;
Plutor
grep returns an array. But only in array context.
innaM
And in scalar context it returns the number of times the match returned true, so really it doesn't matter who's scalar-izing the array.
Plutor
@Plutor: The docs explicitly say what `grep` does so there's no justification for posting an inaccurate description.
Michael Carman
no, grep returns a list. An array contains a list, but a list isn't an array.
brian d foy
+8  A: 

The parentheses provide a list context for grep. grep will then actually return the list of elements for which the expression was true and not just the number of times the expression was true.

innaM
s/array context/list context/
Sinan Ünür
Way better. Thank you.
innaM
You are welcome.
Sinan Ünür
+5  A: 

I think you are looking for first_index from List::MoreUtils:

use List::MoreUtils qw( first_index );

# ...

my $index = first_index { $_ eq $search_for } @array;
Sinan Ünür
+5  A: 

The grep function behaves differently in list context and scalar context. This documented in perldoc -f grep:

Evaluates the BLOCK or EXPR for each element of LIST (locally setting $_ to each element) and returns the list value consisting of those elements for which the expression evaluated to true. In scalar context, returns the number of times the expression was true.

You can duplicate this on your own with the poorly named wantarray function:

sub my_grep {
    my $sub = shift;
    my @return;
    for my $item (@_) {
        push @return if $sub->($item);
    }
    return @return if wantarray;
    return scalar @return;
}
Chas. Owens
+1  A: 

Also, I think using grep just to find the first instance is a little inefficient since it still has to walk through and run the callback on every element of the array. Especially if your array is long, you may be better off writing a loop or else using List::MoreUtils as mentioned above.