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

This is very simple but I am not able to make it work..I have a string and I want to get only the fourth field which is delimited by space (although the first field may or may not have space before it before it starts..I want to get the fourth field as fast as possible as I have to do it many times..
$_="one two three four five six"; #it may be also be $abc=" one two three four five six" ($fieldfour)=/[^\s+]*\s+[^\s+]*\s+[^\s+]*\s+[^\s+]/; print "\n the fourth field is $fieldfour \n"; exit(0);
but I am not ablre to get it..

Replies are listed 'Best First'.
(ar0n: split methinks) Re: novice
by ar0n (Priest) on Sep 27, 2000 at 13:35 UTC
    Since you want it *fast*, I'd suggest using split:
    $_ = " one two three four five six"; @ary = split; print $ary[3];

    update:
    split splits $_ on whitespace by default.

    To split $str on whitespace do:
    $str = " one two three four five six"; $str =~ s/^\s+//; # remove leading spaces @ary = split /\s+/, $str; print $ary[3];

    [~]

      Thanks! but I have read that pattern matching is much more faster than split..Also =~ is supposed to be a little slower comparison operator. at present I am using temporarily for ex.
      ($a,$b,$c,$d)=split; # I get the value of fourth field in $d and do not use$a,$b,$c
      How is it in comparison to the way suggested by you?Please comment.. Thanks:-MRT
        update:
        I seem to be getting different times on each run (and different winners too), but over the whole split does appear to be the winner. I've changed the regex to a more condensed version.

        #!/usr/bin/perl -w use Benchmark 'timethese'; use strict; print "split: ", use_split(), "\n"; print "regex: ", use_regex(), "\n"; sub use_split { $_ = " one two three four five six"; split; # See Jouke's post (this may be faster). This is dep +recated though. return $_[3]; } sub use_regex { $_ = " one two three four five six"; s/^\s+//; m/^(?:[^\s]+\s+){3}([^\s]+)/; return $1; } timethese(-10, { regex => \&use_regex, split => \&use_split }); timethese(-50, { regex => \&use_regex, split => \&use_split }); timethese(-100, { regex => \&use_regex, split => \&use_split });

        The results (updated to 10, 50 and 100) (...drumroll please...):
        Benchmark: running regex, split, each for at least 10 CPU seconds... regex: 12 wallclock secs (10.64 usr + 0.00 sys = 10.64 CPU) @ 40 +522.18/s (n=431156) split: 10 wallclock secs (10.02 usr + 0.00 sys = 10.02 CPU) @ 37 +290.82/s (n=373654)
        and:
        Benchmark: running regex, split, each for at least 50 CPU seconds... regex: 68 wallclock secs (54.11 usr + 0.13 sys = 54.24 CPU) @ 40 +957.93/s (n=2221558) split: 56 wallclock secs (49.95 usr + 0.05 sys = 50.00 CPU) @ 38 +128.48/s (n=1906424)
        and
        Benchmark: running regex, split, each for at least 100 CPU seconds... regex: 136 wallclock secs (110.38 usr + 0.32 sys = 110.70 CPU) @ + 40548.93/s (n=4488766) split: 112 wallclock secs (100.00 usr + 0.02 sys = 100.02 CPU) @ + 38995.86/s (n=3900366)

        [~]

      Or, if you wish to exclude the @ary
      #!/usr/bin/perl use strict; $_="one two three four"; split; print $_[2];

      Jouke Visser, Perl 'Adept'
        Be careful ... if you'd used warnings you'd see:

        Use of implicit split to @_ is deprecated at <blah blah>.

        If you really want the third field only, why not use print +(split)[2]?

        -dlc

(Dermot) Re: novice
by Dermot (Scribe) on Sep 27, 2000 at 14:48 UTC
    There are a couple of ways to use a regular expression to get at the data that you want to get at. Here is one that you can adapt to your particular needs.
    #!/usr/bin/perl -w $_ = " one two three four "; m/\s*(\w+\s+){3}(\w+)/; $field4 = $2; print $field4;
    The m stands for match and is optional but it can be good to leave it there for the sake of clarity. The match operator will by default check against the $_ variable. To generalise it you use $genvar =~ m/pattern/.

    What you are searching for is something along the lines of:

    • Some optional whitespace at the start of the string.
    • Followed by wordy stuff and some whitespace three times.
    • Followed by wordy stuff that you wish to capture.
    • After that you don't care, it could be followed by anything.
  • The \w class shorthand usually stands for [a-zA-Z0-9_] so if your fields will contain anything else you will have to specify that. It can be different depending on your locale setting. Anything in brackets within the regular expression is captured into the special regular expression variables $1, $2, $3 etc and they are available to use after the expression has matched. In this case $1 would be the first three words and whatever whitespace follows them but we are not using this variable afterwards, we only use $2 which contains the fourth field when there is a match. If you know your data well then you could rely on there always being a successful match but this is unsafe coding. Even if you do know your data well you are better off assuming that something can go wrong and check using an if statement that the match did actually succeed.

    Update: I really should have used a ^ to anchor the regex to the start of the line. Otherwise it won't work properly if there are some punctuation characters at the start of the string. The regex above assumes that each line consists of only whitespace and word characters. Your mileage may vary.