http://www.perlmonks.org?node_id=221857

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

Happy Holidays!

Just seeing who else spent all their time off during the year and are stuck working the holidays. For those of you left, let's see if we can give this descent young fella a hand.

My program fills an array with a text file that looks like this:
1001,choochoo
1002,candycane
1003,sockpuppet
etc.

The actual amount of elements in this array may vary, so I can't say $array[2] to be sure of getting a sockpuppet. I need to search the array. Fortunately, I know that the data I'm looking for begins with 1006. What I'd like to do is put the value I'm looking for into a string for further use. (info that needs to be sent back through a URL - junk like that).

So, exposing my weak knowledge of grep and search, I tried the following:
my $answer= grep /^1006,/, @array; my $thing = substr($answer, 6,6);
I know, I know. Pathetic ain't it?

Please help!

peppiv

Replies are listed 'Best First'.
Re: Getting element of array with a match
by broquaint (Abbot) on Dec 23, 2002 at 14:49 UTC
    /me proffers
    my %hash = map { chomp; split /,/ } <DATA>; print $hash{1006},$/; __DATA__ 1001,choochoo 1002,candycane 1003,sockpuppet 1004,choochoo 1005,candycane 1006,sockpuppet6 1007,foo 1008,bar
    Or am I missing the point?
    HTH

    _________
    broquaint

Re: Getting element of array with a match
by tachyon (Chancellor) on Dec 23, 2002 at 14:42 UTC

    While grep is handy it it not efficient as you always search the whole array even if you find a match in the first element. Assuming your '100X' are unique product ids this is probably the most efficient way (although by no means the only way) to do it (the last means we stop searching at the first match. If we find a match string will be defined. If there is not match it will be undef. If you have comma separated data split will work fine. Substring works on the assumption of fixed width records so if someone enters say '1010   ' you would end up getting ' ,string' back which is not what you want. The correct syntax to get all the stuff after 5 chars (4 digits and the comma) is $string = substr $line, 5;

    Your grep is broken because when you say $scalar = grep... you get the number of matches not the actual array elements(s). If you call grep in array context @ary = grep... then you get an array of all the elements that matched - if there is only one it will be $ary[0]. Many functions in perl exhibit this schizophrenic behaviour - it is called scalar/array context.

    my @ary = <DATA>; my $find_id = 1006; my ($code, $string ); for my $line( @ary ) { next unless $line =~ m/^$find_id/; chomp $line; ( $code, $string ) = split ',', $line; last; } if ( $string ) { print "Found '$string'\n"; } else { print "No match!\n"; } __DATA__ 1001,choochoo 1002,candycane 1003,sockpuppet 1004,choochoo 1005,candycane 1006,sockpuppet6 1007,foo 1008,bar

    cheers

    tachyon

    s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

    Fixed errant [] chars dvergin 2002-12-23

      The thing that bugs me most about that snippet is the match-chomp-split sequence. I prefer:

      my $id = 1006; my $answer; for (@data) { last if ($answer) = /^\Q$id\E\s*,(.*)/o; }

      — Arien

      Update: Added /o and allowed for optional whitespace between id and comma per tachyon's worries below.

        Sure, that's the sort of way I would usually code it myself but you then have to (potoentially) explain how that single line works. There is also an implicit requirement in your code that the comma imediately follows the id as well as having no leading whitespace. All probably true but... You should have either used qr// or /o in your code BTW ;-)

        For those that are wondering the \Q...\E is the regex version of quotemeta which ensures that interpolated strings do not blow up your regex when they contain metachars. It is always sound practice to use \$Q$string\E in regexes.

        cheers

        tachyon

        s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

Re: Getting element of array with a match
by dreadpiratepeter (Priest) on Dec 23, 2002 at 14:51 UTC
    Actually a simple loop might be cleaner and clearer in this instance:
    my $thing; foreach (@array) { if (/^1006,(.*)/) { $thing=$1; last; } }
    or even:
    my $thing; foreach (@array) { my ($num,$value) = split /,/; if ($num == 1006) { $thing=$value; last; } }
    BTW, your code above won't work because grep returns the number of matches in scalar context.

    -pete
    "Worry is like a rocking chair. It gives you something to do, but it doesn't get you anywhere."
Re: Getting element of array with a match
by robartes (Priest) on Dec 23, 2002 at 14:53 UTC
    Close, but grep returns a list, not a scalar. Forcing the return value in a scalar, as you do, will get you the number of elements in it, or in your case the number of elements starting with 1006. Also, you might look into split to get to the second field in the element:
    print (split /,/,$_)[1] for grep /^1006/, @array; #UNTESTED
    As a better solution for this problem, consider using a hash instead of an array to store your data (that is, if the first field in your data is unique). Hashes are built for fast text searches:
    use strict; my %hash=qw(1001 choochoo 1002 candycane 1003 sockpuppet); print $hash{'1003'}; __END__ sockpuppet

    CU
    Robartes-

      A variation on the theme of grep
      my @ar = map { chomp; $_ } <DATA>; print +(split /,/, $ar[ grep { /^(\d+)/ and $1 < 1006 } @ar ])[1], $/; __DATA__ 1001,choochoo 1002,candycane 1003,sockpuppet 1004,choochoo 1005,candycane 1006,sockpuppet6 1007,foo 1008,bar
      This of course blindly assumes that you're data is a linear, contiguous set of numbers.
      HTH

      _________
      broquaint

        Thank you everyone for the help and education. I got what I needed and everything works perfectly!

        peppiv