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

Hi All,
  I've written some code to find a values position in an array:-
my $position; for ($position = 0; $position <= $#@array; $position++) { if ($array[$position] eq $value) { last; }#if }#foreach
I'm sure I read somewhere of a quicker way of doing it, but can't for the life of me find it.

Lyle

Replies are listed 'Best First'.
Re: Finding values position in array
by FunkyMonk (Bishop) on Apr 13, 2008 at 23:06 UTC
    From first_index in List::MoreUtils:
    Returns the index of the first element in LIST for which the criterion in BLOCK is true. Sets $_ for each item in LIST in turn:
    Update:
    use List::MoreUtils qw/first_index/; my @array = ( 6, 5, 4, 3, 2, 1 ); my $first_4_idx = first_index { $_ == 4 } @array; print $first_4_idx;

Re: Finding values position in array
by dragonchild (Archbishop) on Apr 13, 2008 at 23:03 UTC
    grep on the indices or first() from List::Util.

    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: Finding values position in array
by starbolin (Hermit) on Apr 13, 2008 at 23:53 UTC

    Anytime you find yourself writing:

    for (list) { last if (); }

    Think grep


    s//----->\t/;$~="JAPH";s//\r<$~~/;{s|~$~-|-~$~|||s |-$~~|$~~-|||s,<$~~,<~$~,,s,~$~>,$~~>,, $|=1,select$,,$,,$,,1e-1;print;redo}

      unless 'list' is large or the test has side effects or is expensive.

      grep processes all of the list whereas the for loop stops on the first successful match.


      Perl is environmentally friendly - it saves trees
      I though of grep but when I checked the doc page (which is thin to say the least) I couldn't see a way of using it. Would you be kind enough to give an example?
        ben@Tyr:~$ perl -we'@list = 1..24; print "'4' found at \$list[$_]\n" f +or grep { $list[$_] =~ /4/ } 0 .. $#list' 4 found at $list[3] 4 found at $list[13] 4 found at $list[23] ben@Tyr:~$ perl -we'@list = 1..24; print "'4' found at \$list[$_]\n" f +or grep { $list[$_] =~ /^4$/ } 0 .. $#list' 4 found at $list[3]

        The first example matches '4' anywhere in the element; the second one is a precise match.

        
        -- 
        Human history becomes more and more a race between education and catastrophe. -- HG Wells
        
Re: Finding values position in array
by starbolin (Hermit) on Apr 14, 2008 at 02:45 UTC

    Define quicker. foreach() is quicker but mapping to a hash allows for multiple lookups and is less typing.


    s//----->\t/;$~="JAPH";s//\r<$~~/;{s|~$~-|-~$~|||s |-$~~|$~~-|||s,<$~~,<~$~,,s,~$~>,$~~>,, $|=1,select$,,$,,$,,1e-1;print;redo}

      The issue is more about appropriate tools for the job than hammering various shaped pegs into whatever hole you have.

      • use grep to get an output list that is a subset of an input list
      • use map to transform an input list into an output list
      • use for to iterate over a list (and almost never use the C version of for)
      • use a hash to look up keyed data quickly
      • use an array to lookup indexed data quickly
      • because hash keys are unique a hash is a good tool for finding unique strings in a bag of strings

      Perl is environmentally friendly - it saves trees
Re: Finding values position in array
by starbolin (Hermit) on Apr 14, 2008 at 06:18 UTC

    You could also refer to the very complete Getting Matching Items From An Array.

    At first I said, "of course GrandFather is right", but upon further testing the situation is not so clear. The optimizations in grep give it an edge over the C style for if you have to look approximately 1/2 way into the array. This did not surprise me as much as the poor performance of a binary search. ( code borrowed from Binary search). Though I think after struggling to coax grep to return a list value into a scalar context I would avoid that solution unless you really want multiple values returned.

    oko1 gives an implementation of grep but I was thinking more something like: grep {$array[$_] eq $value} (0..$#array) as shown in the code below. Which, as I said, may be just quick enough.


    s//----->\t/;$~="JAPH";s//\r<$~~/;{s|~$~-|-~$~|||s |-$~~|$~~-|||s,<$~~,<~$~,,s,~$~>,$~~>,, $|=1,select$,,$,,$,,1e-1;print;redo}

      You should maybe point out that your subs are not actually equivalent.

      If $value occurs more than once in @array, then maptohash will return the index of the last occurrence, while the others will return that of the first occurrence.

        No need to as Not a Number has done it for me ;-)>

        Also significant is in some application is that the run-times of map_to_hash and a binary search are constant over the solution set where as any of the iterators will be significantly faster/slower depending on $value. I was thinking all this as I was coding, and that is part of the fun of this whole programming business, but I was just trying to get something to run with some kind of relevance to the OP's question.

        I find myself using grep or map more and more and almost never use the C style for() loop. I like the programming flexibility that mapping the indexes into a hash gives me even though that is the slowest as far as the run-times.

        As an example, the run-times of my current project are almost totally dominated by the time required to seek to the files. I am just about finished rolling all the file accesses into their own loops using predominately greps and maps. Something like this only using a HOH

        @file_handles = map { some_code_to_open_files } @huge_filelist; @wanted_files = grep { some_file_access } @file_handles; @wanted_data = grep { some_more_file_access } @wanted_files; @return_codes = map { close $_ } @file_handles;

        This has the effect of eliminating a lot of nested parenthesis and makes the code look 'flat'. It also forces the procedural code outside of the loops and the data access code inside the loops. This drives the code naturally toward an OO style and will allow me to re-factor what was a very procedural script into OO code. What's more I've tested the results and the run-times are identical.

        I don't claim credit for this approach. Most of this was not my own idea. I got the inspiration from reading "Intermediate Perl".


        s//----->\t/;$~="JAPH";s//\r<$~~/;{s|~$~-|-~$~|||s |-$~~|$~~-|||s,<$~~,<~$~,,s,~$~>,$~~>,, $|=1,select$,,$,,$,,1e-1;print;redo}

        No need to as Not a Number has done it for me ;-)>

        Also significant is some application is that the run-times of map_to_hash and a binary search are constant over the solution set where as any of the iterators will be significantly faster/slower depending on $value. I was thinking all this as I was coding, and that is part of the fun of this whole programming business, but I was just trying to get something to run with some kind of relevance to the OP's question.

        I find myself using grep or map more and more and almost never use the C style for() loop. I like the programming flexibility that mapping the indexes into a hash gives me even though that is the slowest as far as the run-times.

        As an example, the run-times of my current project are almost totally dominated by the time required to seek to the files. I am just about finished rolling all the file accesses into their own loops using predominately greps and maps. Something like this only using a HOH

        @file_handles = map { some_code_to_open_files } @huge_filelist; @wanted_files = grep { some_file_access } @file_handles; @wanted_data = grep { some_more_file_access } @wanted_files; @return_codes = map { close $_ } @file_handles;

        This has the effect of eliminating a lot of nested parenthesis and makes the code look 'flat'. It also forces the procedural code outside of the loops and the data access code inside the loops. This drives the code naturally toward an OO style and will allow me to re-factor what was a <st>very</st> procedural script into OO code. What's more I've tested the results and the run-times are identical.

        I don't claim credit for this approach. Most of this was not my own idea. I got the inspiration from reading "Intermediate Perl".


        s//----->\t/;$~="JAPH";s//\r<$~~/;{s|~$~-|-~$~|||s |-$~~|$~~-|||s,<$~~,<~$~,,s,~$~>,$~~>,, $|=1,select$,,$,,$,,1e-1;print;redo}