tags:

views:

144

answers:

3

I have a string which contains "foo"s followed by numbers. I'm looking to print the number following the last foo in the string. I've been told that the only way to accomplish this is to reverse the string itself. This isn't very elegant, and I'm surprised Perl doesn't have a better way to get the job done. Is there a better way to do it than this?

#!/usr/bin/perl
# works, but is ugly!!!
$string = "foo1 foo3 foo5 bar foo911 baz";
$string = scalar reverse($string);
$string =~ m/(\d+)oof/;
print scalar reverse("$1");
+9  A: 

How about:

$string =~ /.*foo(\d+)/;

Clarification:

$string =~ /.*     # Match any character zero or more times, greedily.
            foo    # Match 'foo'
            (\d+)  # Match and capture one or more digits.
           /x;

The greedy "any character" match will match the first "foo"s in the string, and you'll be left just matching the last "foo".

Example:

#!perl -w

use strict;
use 5.010;

my $string = "foo1 foo2 foo3";
$string =~ /.*foo(\d+)/;
say $1;

Output:

% perl regex.pl
3
CanSpice
this solutions is easier to read, but how does it compare to the "reverse the string" solution in terms of efficiency or how long it will take the computer?
Corey Jeffco
In your case you need to do two `reverse`s and a regex. In my case I need to do a regex. Which do you think will be quicker?
CanSpice
good point! I bet yours will be faster
Corey Jeffco
@Corey: When faced between making something faster or easier to read, go with easier to read unless you have a really compelling reason otherwise (such as having benchmark numbers in hand). Almost always, you save far more time by making things more efficient for the programmer than for the computer.
Ether
$me = 1 while $var =~ /foo(\d*?)/g; $me = $1; # non-greedy variation, only last found foo survives. Notice /g modifier.
mhambra
With this regex, the efficiency depends on how close to the end of the string the final foo is. The engine will first match the whole string (the whole string will be consumed by `.*`, since it's *greedy*), and then backtrack one step at a time until `foo\d+` is matched, which means that the further the final foo is from the end of the string, the more backtracking the engine will need to do.
Daniel Vandersluis
another variation: `/foo(\d+)(?!.*?foo)/s`; finds foo+digits, then checks that there is no later foo
ysth
@CanSpice: which will be quicker isn't that simple. If there's likely to be a foo near the end of the string, your regex may win; if it's a very long string and the last foo is near the beginning, it wouldn't surprise me if the double reverse won.
ysth
+3  A: 

Here's another solution, inspired by ysth's comment (if it's a very long string and the last foo is near the beginning, resulting in the regex being slow): split the line on 'foo' and parse the last element for the numbers:

my @results = split /foo/, $string;
my ($digits) = ($results[-1] =~ m/^(\d+)/);

Again, I would always go with the simplest code until it looked like the code was taking too long (and this was a problem in the overall application), and then I'd benchmark a number of solutions against typical inputs to see which is best.

Ether
This will fail to get the same result as the OP if `$string = 'foo123 foo456 foo';`. Maybe include an assertion that foo has to be followed by a digit: `/foo(?=\d)/`
Ven'Tatsu
@Ven: that doesn't seem to match the OP's criteria: "I'm looking to print the number following *the last foo in the string*."
Ether
@Ether, your answer may be correct for the OP's needs, I was just stating that there is an edge case where your code and the OP's differs, along with a way to adjust your code to match the OP's behavior if needed.
Ven'Tatsu
@Ven: how does my original code not match the OP's behaviour? he specified "number following the last foo", not "last number following a foo". Granted it doesn't match the behaviour of the highest-voted answer, but that doesn't match the OP's stated requirement either :)
Ether
@Ether, I meant that if you ran your code and the OP's code against the string in my first comment they would have different results. Again, your code *may* do what the OP wants. However I have developed a healthy distrust of written specs when they come in conflict with existing implementation. Quite often people don't think about the exact behavior they want when the describe a problem in english. I have to assume there is a chance the OP meant to have `m/(\d+)oof/` and not `m/(\d*)oof/` that the text describes.
Ven'Tatsu
+4  A: 

I know you already picked an answer but I thought I would add my $0.02.

Why not do a list context global pattern match and take the last element:

#!/usr/bin/perl

use strict;
use warnings;

my $string = "foo1 foo2 foo3 bar";
my @result = $string =~ /foo(\d+)/g;

print pop(@result) . "\n";
Joel
`[\d+]` doesn't do what you think it does.
Ether
You're right. It did work, but [] not necessary and probably could cause an odd problem here or there. Fixed.
Joel
`[\d+]` will match any *one* digit *or* a plus. For the OP's "foo1 foo3 foo5 bar foo911 baz" it will capture '9' not '911'. It will also incorrectly match "foo+".
Ven'Tatsu
@Ven, you are correct, and that is why I changed my source to remove the square brackets. Originally I had meant `[\d]+` which would have worked but had two more characters than necessary
Joel