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

Hello Respected Monks,

In my program I have two functions. Both are almost the same except that one extracts the value of $1 from the regex m/\s*(\S+)\s*(\S+)\s*(\S+)\s*(\S+)\s*/ and the other extracts the value of $2.
So I thought why not merge these into one function. and pass the column number which I want to extract as a parameter? So I wrote code like

if ($columnNumber == 2) { push @sps, $2 if ($2); } elsif ($columnNumber == 3) { push @sps, $3 if ($3); }
but I don't like this ifelse approach. Can't I do it shorter by avoiding this if else?
Something like
push @sps, $($columnNumber) if ($($columnNumber));
So that the columnNumber automatically substitues itself inside the $1/$2/$3 expression.
But this code is wrong and doesn't even compile How can I make a variable substribute the 1 in $1.

Replies are listed 'Best First'.
Re: How to remove the $1 hard coding
by Fletch (Bishop) on Aug 22, 2003 at 15:45 UTC

    In general when you want to vary the name of a variable it's a sign you probably want an array or hash instead.

    my @res = m/\s*(\S+)\s*(\S+)\s*(\S+)\s*(\S+)\s*/ ; push @sps, $res[ $columnNumber ];

    Update: As flounder99 notes (in the wrong direction :) you might need to adjust $columnNumber down by one to get the right index it starts at one rather than zero.

      Elegant solution. Assuming that the search string is not $_ (and is $string) they probably want:

      my @res = $string =~ m/\s*(\S+)\s*(\S+)\s*(\S+)\s*(\S+)\s*/ ; push @sps, $res[ $columnNumber ];

      However a regex is total overkill when you could just:

      @res = split ' ', $string; push @sps, $res[ $columnNumber ]; # or if you want one line push @sps, (split ' ', $string)[$columNumber];

      Update

      Fixed typo thanks chunlou

      cheers

      tachyon

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

        The split is not the same as the pattern. The pattern uses \s* as the delimiter, which means that ABCD should match that pattern, as well as "A B C D".

        I just tested this, and it fails:

        push @sps, ($string =~ m/RE/)[$columnNumber];

        Could some fine monk explain why? Wouldn't the RE evaluate in a list context, which would then have an element extracted from it?

        ...$string = m/...
                    ^
                    |
                   typo?
        
      I think you want
      push @sps, $res[ $columnNumber + 1 ];
      since abhishes was using $columnNumber == 2 for $2 then $columnNumber appears to be one-based not zero-based like an array.

      --

      flounder

Re: How to remove the $1 hard coding
by hardburn (Abbot) on Aug 22, 2003 at 15:51 UTC
    no strict 'refs'; push @sps, $$columnNumber if ($$columnNumber);

    *ducks*

    ----
    I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
    -- Schemer

    Note: All code is untested, unless otherwise stated

Re: How to remove the $1 hard coding
by CombatSquirrel (Hermit) on Aug 22, 2003 at 16:00 UTC
    You could do
    { no strict 'refs'; $somestring =~ m/\s*(\S+)\s*(\S+)\s*(\S+)\s*(\S+)\s*/; $result = ${$matchnum}; }
    but it is strongly discouraged (forget about it immediately), and won't even work under strict (that's the reason for the no strict 'refs';). Fletch's answer provides a much better solution, to which I could just add that you can avoid copying the matches to an array, if you treat the return values of the match as a list:
    push @sps, (m/\s*(\S+)\s*(\S+)\s*(\S+)\s*(\S+)\s*/)[$columnNumber];
    should work fine.
    Cheers, CombatSquirrel.

      Actually, after thinking about it, I don't think a symbolic ref solution would be so bad in this case, provided you take a few percautions. At the top of your subroutine, make sure $matchnum matches against /\A\d+\z/ and that $$matchnum is a defined value after the match (change your last line to $result = $$matchnum if $$matchnum;).

      Though overall, you're still probably better off with the array solution.

      ----
      I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
      -- Schemer

      Note: All code is untested, unless otherwise stated

        You meant, of course,   $result = $$matchnum if defined $$matchnum;.   And while I agree the array approach is better, your mention of $$matchnum "made me look".   I hadn't thought the $1 variables were _that_ global.
Re: How to remove the $1 hard coding
by Anonymous Monk on Aug 22, 2003 at 21:32 UTC
    sub a{ my($col,$string) = @_; ($string =~ /(.)(.)(.)(.)/)$col } a(2,"abcdefg")
Re: How to remove the $1 hard coding
by jonadab (Parson) on Aug 25, 2003 at 17:52 UTC
    one extracts the value of $1 from the regex m/\s*(\S+)\s*(\S+)\s*(\S+)\s*(\S+)\s*/ and the other extracts the value of $2. So I thought why not merge these into one function and pass the column number which I want to extract as a parameter?

    You're thinking along the right lines. My first suggestion would be to do just what you said: combine it into one function...

    sub nthword { my ($texttomatch, $fieldnum) = @_; ($texttomatch =~ m/\s*(\S+)\s*(\S+)\s*(\S+)\s*(\S+)\s*/)[$fieldnum]; }

    As another poster points out, this will be indexed from zero, so if you want it indexed from one use $fieldnum+1 in the brackets.

    My second thought is that the regular expression is very regular. If this is really the regex you're working with, you might be able to use split to further simplify your code.

    sub nthword { my ($texttomatch, $fieldnum) = @_; (split /\s+|^/, $texttomatch)[$fieldnum]; }

    This is now simple enough it could almost be inlined. It is not exactly the same, but if your data is the way I guess it might be it will be equivalent. (Where you run into differences is if your original regex is sometimes running off the end of the string, backtracking, and matching the last instances of \s* against the null string in order to get a match. But I'm guessing that if it does that it's not what you intended anyway, so my version should be fine.)


    $;=sub{$/};@;=map{my($a,$b)=($_,$;);$;=sub{$a.$b->()}} split//,".rekcah lreP rehtona tsuJ";$\=$ ;->();print$/