http://qs1969.pair.com?node_id=514155

matt.tovey has asked for the wisdom of the Perl Monks concerning the following question:

This is somewhat embarassing, since I'm sure the answer to this question is to be found in many good perl books. But I suspect the answer is probably pretty simple to someone who has the "perl mentality", so I'm hoping your replies will aid me in gaining said mentality. I used to program Tcl, and since changing to perl, often find myself wanting an 'lsearch' function. I tend to add it so:
sub lsearch { my $item = shift; my @array = @_; foreach (0..$#array) { if ($item eq $array[$_]) { return $_; } } return ""; } # Want to remove $item from @list unless (my $index = lsearch($item, @list) eq "") { splice(@list, $index, 1); }
I think that in some cases where I used to use 'lsearch', I could now be using 'grep', but what do you do when you want the index of an item in your list? Or do you just program to avoid such cases?

Replies are listed 'Best First'.
Re: lsearch for perl?
by ikegami (Patriarch) on Dec 05, 2005 at 15:45 UTC
    $found = 0; @list = grep { $item ne $_ || $found++ } @list;

    or

    my ($index) = grep { $item eq $list[$_] } 0..$#list; splice(@list, $index, 1) if defined $index;

    or

    use List::Util qw( first ); my $index = first { $item eq $list[$_] } 0..$#list; splice(@list, $index, 1) if defined $index;

    Update: Tested. Fixed flawed grep rule.

      or
      use List::MoreUtils qw( first_index ); my $index = first_index { $item eq $_ } @list; # Returns -1 if no such item could be found.
      Thanks to all for your replies - they were all very helpful.

      Things like this

      @list = grep { $item ne $_ } @list;
      aren't in the man-page, and got me thinking. This however:
      my ($index) = grep { $item eq $list[$_] } 0..$#list; splice(@list, $index, 1) if defined $index;
      was a real satori, Keanu-Reeves-like "Whoah!!" moment for me. The idea of using grep on a list without naming _that_ list as the argument to grep would never have occurred to me. Guess I was stuck in Unix grep thinking!

      Feeling quite humbled.

        Things like this aren't in the man-page

        grep accepts any code in the block. It doesn't have to be a regex. grep is useful for filtering out items from a list, while map is useful for transforming a list.

        The idea of using grep on a list without naming _that_ list as the argument to grep would never have occurred to me.

        It's an concept I picked up on PerlMonks too :)

Re: lsearch for perl?
by Zaxo (Archbishop) on Dec 05, 2005 at 15:46 UTC

    Sure, grep will simplify that, with a minor change in behavior - maybe for the better.

    sub lsearch { my $item = shift; grep { $item eq $_[$_] } 0 .. $#_; }
    That will return the empty list for false, and otherwise a list of each index that succeeds

    Usage becomes, for your example,

    for (reverse lsearch $item, @list) { splice @list, $_, 1 } # or splice @list, $_, 1 for reverse lsearch $item, @list;
    so all instances of $item are removed from the array. The reverse call is needed to preserve the indexes from being shifted out of sync by splice.

    An alternative is to define a winnow() function to do it all:

    sub winnow { my $item = shift; grep { not $item eq $_ } @_; } @list = winnow $item, @list;
    That is maybe less flexible, but I think cleaner.

    After Compline,
    Zaxo

Re: lsearch for perl?
by VSarkiss (Monsignor) on Dec 05, 2005 at 16:16 UTC

    You have some excellent examples above of how to achieve what you asked for. But I want to address your second point:

    Or do you just program to avoid such cases?

    That's really the heart of the matter. In Perl, it's much more convenient to iterate over array or list items directly, rather than trying to refer to them by index. If you find yourself writing:

    for my $i (0 .. $#array) { if ($array[$i] eq ... ) {
    immediately stop and change that to:
    for my $i (@array) { if ($i eq ... ) {
    In the long run, it'll help you to avoid off-by-one problems as well.

    Of course, there are situations where you really do need array indexes, but that need is much rarer in Perl than in, say, C, or Tcl, or similar languages.

Re: lsearch for perl?
by pileofrogs (Priest) on Dec 05, 2005 at 16:59 UTC

    While this isn't really an answer to the "lsearch" question, it does address the "perl mentality" part of the question

    When I first started using perl (after some time in C) I thought hashes were black magic and I did a lot of stuff with lists that I would now do with a hash. In fact, I use lists pretty rarely any more.

    When you're using lists, ask yourself if it could be done more easily with a hash.

    -Pileofrogs

    Updated: Yes! Thank you thor and ikegami. I think that was what I was trying to say.

      When you're using lists, ask yourself if it could be done more easily with a hash.
      In my opinion, they address different problems. Arrays are an ordered set of things, hashes are an unordered set of associations. Sure, if you find yourself doing something like this:
      my (@item, @color); while(<DATA>) { chomp; my($item, $color) = split(/,/); push @item, $item; push @color, $color; } for(my $i=0; $i < @item; $i++) { print "$item[$i] has color $color[$i]\n"; } __DATA__ apple,red orange,orange banana,yellow
      then yes...use a hash. Don't try to shoehorn it, though.

      thor

      The only easy day was yesterday

        While I agree that hashes wouldn't be too useful here (being unordered), I think of hashes as having three basic uses (not one):

        • Unordered set of associations (Notice the plural var name)
          my %user_passwds = ( joe => ..., jeff => ..., john => ..., );
        • Unordered set of properties (Notice the singular var name)
          my %user = ( id => 'joe', name => 'Joe', passwd => ..., home_dir => ..., );
        • Uniqueness constraint
          my %unique; ++$unique{$_} foreach @list; @list = keys %unique;
        Arrays have many uses and sometimes perl programmers can overuse hashes, but as an ex-Tcl programmer myself I can give the advice to:
        1. use hashes when you can
        2. when an array is appropriate, use foreach, map and grep. Some perl programmers might overuse map and grep, but when they are good they are VERY good!
        3. if you find yourself using an array index ask yourself why - there is almost always a better way in perl
        For the enlightenment of non-Tcl people, Tcl has only two types of data. Strings and lists. That's it. Even function calls are just a list where the first list value is the name (string) of a function (or proc) and the rest of the list values are the arguments (which may be, you guessed it, strings or lists).

        The lists aren't even very convenient, vis. lsearch.

        Don't think I'm bashing Tcl - it's very useful when you need a straightforward embeddable scripting language. You can even make it do very non-trivial things (vis. OpenACS) but it hurts.

        upvar anyone?

Re: lsearch for perl?
by jbullock35 (Hermit) on Dec 05, 2005 at 19:50 UTC
      INDICES PEOPLE INDICES