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

Morning fellow monks !

I have a function for checking that an item is a list member:
sub isListMember { my ($array, $item) = @_; # array ref, item for (@{$array}) { return $TRUE if ($_ eq $item); # the problematic comperison } return $FALSE; }
Can I change this functions somehow to work also with numeric values (without getting any warnings of course)?, or I must write a function for numeric values also

Thanks.

Hotshot

Replies are listed 'Best First'.
Re: Is list member
by ariels (Curate) on May 06, 2002 at 08:15 UTC

    No, you cannot write a single function that will do both. You must have some notion of what "equality" means before you can write this function. Say we denote such equality (in English, not in Perl) with the symbol `~'. You'll need to decide e.g.

    • '10' ~ 10?
    • 'x10' ~ 0?
    • '' ~ 0?

    It's also not clear from your question if you want the same function to work for finding both numbers in a number list and strings in a string list, or if you'd also like it magically to "do the right thing" when searching for a string in a number list etc.

    Consider what you want to do, and then replace your problematic comparison e.g. with one of these:

    • "$_" eq "$item"
    • 0+$_ == 0+$item
    • The same, but <samp>local</samp>ly switching $^W off.

    For an all-singing, all-dancing run-time customizable (and slower) solution, pass in a function to perform the comparison:

    sub member(&@$) { my ($eq, $array, $item) = @_; for (@$array) { return 1 if $eq->($_, $item) } return undef; }

    Finally, why are you trying to circumvent Perl's booleans? The use of things like $TRUE and $FALSE is a bad idea. First, builtins might not return these values, so you end up with 4 different values, and cannot do stuff like "return $a == $b" in the routine you pass to member. The values of TRUE and FALSE aren't going to change in the lifetime of your program; if they do, all your code will fall apart, so it doesn't matter. And note you're still using Perl's booleans, not yours:

    ... if ($_ eq $item)
    should really test for truth, so it should be
    ... if ($_ eq $item) == $TRUE
    which should really test for truth, so it should be
    ... if (($_ eq $item) != $FALSE) eq $TRUE
    etc.

Re: Is list member
by JayBonci (Curate) on May 06, 2002 at 09:08 UTC
    Stringification is your friend. You can use double quotes to have any item thrown into string context. It won't matter if it's another string or not, such as:
    #!/usr/bin/perl -w use strict; my @stuff = ("foo", 1, "3", "5", 8, "jerk", "bummer"); my $test = "1"; foreach(@stuff){ print "Found!\n" if ("$test" eq "$_"); }
    Note tossing $test and $_ into strings solves all sorts of internal numerical <=> issues. I think it always comes up with the same string that you'd get as if you print'ed it. However, if you're looking for numerical equivalency (ie, you're passing in things like "0e0" and expecting it to match to it's equivalent), then you're probably out of luck. This is for ultra simple data sets. Just another way of thinking about it...

        --jb
Re: Is list member
by erikharrison (Deacon) on May 06, 2002 at 08:17 UTC

    Actually, that IS how you handle numeric values. Perl is absolutely typeless - meaning that it implicitly stringifies and numifies variables in the right context. Now, numification often gets you 0 which makes alot of things match where you want them not to. However, stringification will usually get matches properly, when comparing two numbers.

    ps: Perl 6! $foo =~ @array

    Cheers,
    Erik

      Careful, there are unequal strings that are equal as numbers:

      $ perl -e'$c="10e0";$d="1e1";print $c == $d,$/' 1 $ perl -e'$c="10e0";$d="1e1";print $c eq $d,$/' $ perl -e'$c=10e0;$d=1e1;print $c eq $d,$/' 1 $ perl -e'$c=10e0;$d=1e1;print $c == $d,$/' 1
      I think this question is best solved by redesigning it away.

      After Compline,
      Zaxo

Grep ?..
by yodabjorn (Monk) on May 06, 2002 at 10:07 UTC
    couldnt you use grep to do this ?

    #!/usr/bin/perl -w # # quick test to match an item in an array use strict; my $match = shift ; my @arr = qw( 2 3 4 5 6 7 foo foo12 12foo 1foo1) ; print "match \n" if ( grep(/\b$match\b/, @arr) ) ;

    1 as input will not match
    foo as input will not match
    2 as input matches!

    works for me.. is this a bad way to go about it ?
    my regex may not be optimal. regex is not my strong point. I just got the book on it. It's next after "Data Munging with perl"
      Personally I would say that as long as the intention is find a single element in a list then grep is not the ideal. grep is more useful when you want to find all the elements in a list that match some criteria. Using it to find one element usually means that grep is doing a whole lot more work than is necessary. (It wont stop once its found the element even if it were the first.)

      Yves / DeMerphq
      ---
      Writing a good benchmark isnt as easy as it might look.

Re: Is list member
by hotshot (Prior) on May 06, 2002 at 07:55 UTC
    Silly me, it's obvious how to do that, I'v just changed it too:
    sub isListMember { my ($array, $item) = @_; # array ref, item for (@{$array}) { if (/^\d+$/) { return $TRUE if ($_ == $item); } else { return $TRUE if ($_ eq $item); } } $return $FALSE; }
    Is that the right way, or there's a better or correct one?

    Thanks.

    Hotshot

      You realise that this only works for integers?

      --
      <http://www.dave.org.uk>

      "The first rule of Perl club is you do not talk about Perl club."
      -- Chip Salzenberg