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

I have a 2D array structured as below. I want to search the first column only for "goat" and return the index value, in this case 2. However, using map or grep, it returns 1. Can anyone help me resolve this conundrum. The only way I can think of this is with a for loop that checks each item at column 0 in the array. However, I would like a faster processing method if at all feasible. Thank you
#!/usr/bin/perl use strict; use warnings; my @array=(); $array[0][0] = "ape"; $array[0][1] = "bear"; $array[0][2] = "cat"; $array[1][0] = "dog"; $array[1][1] = "emu"; $array[1][2] = "fox"; $array[2][0] = "goat"; $array[2][1] = "horse"; $array[2][2] = "ibex"; my $result2 = map { $array[$_][0] =~ /goat/ ? $_ : () } 0..$#array; my $result3 = grep { $array[$_][0] =~ /goat/ } 0..$#array; print "R2: $result2 R3: $result3 \n";

Replies are listed 'Best First'.
Re: Index 2D Array
by choroba (Cardinal) on Dec 09, 2021 at 16:47 UTC
    map or grep in scalar context return the number of elements. Scalar assignment enforces scalar context. Use parentheses for list context:
    my ($result2) = map { $array[$_][0] =~ /goat/ ? $_ : () } 0..$#array; my ($result3) = grep { $array[$_][0] =~ /goat/ } 0..$#array;
    or subscript the resulting list:
    my $result2 = (map { $array[$_][0] =~ /goat/ ? $_ : () } 0..$#array)[0 +]; my $result3 = (grep { $array[$_][0] =~ /goat/ } 0..$#array)[0];

    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
Re: Index 2D Array
by kcott (Archbishop) on Dec 10, 2021 at 00:16 UTC

    G'day RHC_ICT,

    "However, I would like a faster processing method if at all feasible."

    The following is based on these assumptions:

    • You're only searching for "goat"; not "goatee" or "goat's milk" or any other string containing the substring "goat".
    • All values in your matrix are unique. In other words, your result won't look like: "'goat" found on rows 2, 13, and 42".

    Here's my tips for faster processing:

    • Use eq to determine an exact match; not a regular expression.
    • Use a for loop instead of map or grep loops.
    • Exit the loop as soon as a match has been found.

    Benchmark to compare the validity of any of those. Here's one I did last week to compare for and map: "Re^5: How Perl can push array into array and then how retrieve [Benchmark]".

    Here's some sample code:

    #!/usr/bin/env perl use strict; use warnings; my @array=(); $array[0][0] = "ape"; $array[0][1] = "bear"; $array[0][2] = "cat"; $array[1][0] = "dog"; $array[1][1] = "emu"; $array[1][2] = "fox"; $array[2][0] = "goat"; $array[2][1] = "horse"; $array[2][2] = "ibex"; for (qw{goat gout dog goatee ape}, "goat's milk") { my $found = search_col(\@array, 0, $_); print "'$_' ", (defined $found ? "found on row $found" : 'not found' ), "\n"; } sub search_col { my ($array, $col, $search) = @_; my $result; for my $row (0 .. $#$array) { next unless $array->[$row][$col] eq $search; $result = $row; last; } return $result; }

    Output:

    'goat' found on row 2 'gout' not found 'dog' found on row 1 'goatee' not found 'ape' found on row 0 'goat's milk' not found

    And benchmark that solution with any other solutions that are presented.

    Finally, bear in mind that speed optimisations on small datasets are almost always a waste of time. If your data really is a 3x3 matrix, I'm sure you could get the correct result by visual inspection, faster than it would take you to type in the command to get the computer to do it for you. In general, aim to run benchmarks on real data, not on tiny samples.

    — Ken

Re: Index 2D Array
by NetWallah (Canon) on Dec 09, 2021 at 18:36 UTC
    Your current data structure makes this search harder than it needs to be.
    Simple, easy-to-read code to work with this:
    my @a2=map { $array[$_][0] } 0..$#array; my ($goat_idx) = grep{ $a2[$_] eq "goat" } 0..$#a2; print "Goat index is $goat_idx.\n";
    (Output is : Goat index is 2.) It will be "undef" if "goat" is not found.

                    "If you had better tools, you could more effectively demonstrate your total incompetence."

Re: Index 2D Array
by Marshall (Canon) on Dec 13, 2021 at 07:10 UTC
    I think others have shown why your code doesn't work (prints 1 instead of 2).

    If speed is of interest, I would use the List::Util qw(first) function. Instead of map or grep which will process the entire input list, "first" will "short circuit" and give up processing the input list when it finds a "match". If on average you are searching for something midway in the list, this will be 2x as fast because on average you are only searching 1/2 the list before finding a match instead of searching the entire list every time no matter what.

    #!/usr/bin/perl use strict; use warnings; use List::Util qw(first); my @array=(); $array[0]= [qw(ape bear cat)]; $array[1] = [qw(dog emu fox)]; $array[2] = [qw(goat horse ibex)]; $array[3] = [qw(mule elephant zebra)]; my $result4 = first{$array[$_][0] =~ /goat/}0..$#array; print $result4."\n"; #prints: 2 # This is logically the same thing, # However, the List::Util function written in C # probably runs much faster foreach my $i (0..$#array) { if ($array[$i][0] =~ /goat/) { print "$i\n"; last; } }
    Update: my $result4 = first{$array$_[0] eq "goat"}0..$#array;
    will be much faster than using the regex engine to test for "goat"

    Update2: I am curious about your application? If you are using linear search and that is an issue, there probably are better data structures. On the other hand, this morning I had a DB application where I needed to build a histogram of one column. My SQL "kung-fu" isn't so hot, so I just read all 1.3M rows and made a Perl hash table. Takes less than 1 second - so what? In my application this one second doesn't matter a bit. All this stuff is application dependent. There are other operations where I ask SQL to do things that would take a lot of Perl code.