jdagius has asked for the wisdom of the Perl Monks concerning the following question:

Hello,

I'm trying to use grep() to locate items in a list (yes, I know about List::Member, but it won't find 0 (zero) in a list, (bug?).

I wrote a function called member() below which returns 1-based location or 0 if not found. It works fine the first time I call it, but fails subsequently. What's going on?
Tnx,
Johanus

sub member { my $item = shift; my @found = grep $_[$_] eq "$item", 0..$#_; if (defined @found) { return $found[0] + 1; } else { return 0; } } ## end member() @x=(0,1,3); print member(2,@x),"\n"; # not in list print member(0,@x),"\n"; # 1st item in list print member(2,@x),"\n"; # not in list, again

> OUTPUT:
0 1 1 # fails, should return 0, again

Replies are listed 'Best First'.
Re: sub fails second time called
by kyle (Abbot) on Apr 23, 2008 at 15:02 UTC

    I recommend you use strict and warnings, first of all. If you run with warnings, it says:

    defined(@array) is deprecated at ... (Maybe you should just omit the defined()?) 0 1 Use of uninitialized value in addition (+) at ... 1

    If I remove the "defined", as suggested, it runs as desired.

Re: sub fails second time called
by Limbic~Region (Chancellor) on Apr 23, 2008 at 15:48 UTC
    jdagius,
    (yes, I know about List::Member, but it won't find 0 (zero) in a list, (bug?)

    Yes, there is a bug and you should file a bug. Be sure to CC the author because not all CPAN authors look at their RT queue.

    The issue is from this line in the source:

    my $target = shift or Carp::croak "No target in member/2 ";
    It assumes that $target will be true - which rules out '', "0", 0, etc. That sub would probably be better written as:
    sub member { my ($target, @list) = @_; Carp::croak "No target in member/2 " if ! @list; if (defined $target) { for (0 .. $#list) { return $_ if $list[$_] eq $target; } return $NEG; } else { for (0 .. $#item) { return $_ if ! defined $item[$_]; } return $NEG; } }

    It would also be nice if the documentation explicitly reminded the user that since it returns the index (first item = 0), member() should not be used as a boolean.

    With regards to your original question - see Getting Matching Items From An Array.

    Cheers - L~R

Re: sub fails second time called
by toolic (Bishop) on Apr 23, 2008 at 15:50 UTC
    Here are two other potential solutions.

    Solution using grep:

    #!/usr/bin/env perl use warnings; use strict; sub member { my $item = shift; my @list = @_; if (grep {$_ eq $item} @list) { return 1; } else { return 0; } } my @x = (0,1,3); print member(2,@x),"\n"; # not in list print member(0,@x),"\n"; # 1st item in list print member(2,@x),"\n"; # not in list, again

    Solution using the any function in List::MoreUtils:

    #!/usr/bin/env perl use warnings; use strict; use List::MoreUtils qw(any); sub member { my $item = shift; my @list = @_; return (any {$_ eq $item} @list) + 0; } my @x = (0,1,3); print member(2,@x),"\n"; # not in list print member(0,@x),"\n"; # 1st item in list print member(2,@x),"\n"; # not in list, again

    Both print out:

    0 1 0
Re: sub fails second time called
by ysth (Canon) on Apr 23, 2008 at 19:08 UTC
    From defined:
    Use of "defined" on aggregates (hashes and arrays) is deprecated. It used to report whether memory for that aggregate has ever been allocated. This behavior may disappear in future versions of Perl. You should instead use a simple test for size:
    As an optimization, perl is reusing the same @found array across multiple function calls, and since the second call to member allocates memory for the array, defined() returns true, even on the third call.

    Since you don't actually need an array of results, you might want to do something like this instead:

    my $found; ($found) = grep ...; if (defined $found) { return $found + 1; } else { return 0; }
      Brother Monks,

      I now understand why the sub failed and have used your ideas to create a correctly working function.

      I also submitted a bug report on List::Member at cpan.org.

      Thanks!
      Johanus