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

I want to transform the LAST matched character (instead of the first) to another character in a string.

For example, if $foo = 'aabbcc' then I need a variation of s/$foo/X/ that will transform $foo into 'aabXcc'.

Thanks in advance!

Replies are listed 'Best First'.
Re: matching operator question
by TimToady (Parson) on May 13, 2005 at 21:36 UTC
    I'm a bit surprised that no one has suggested the standard greedy solution:
    s/(.*)b/$1X/
    Depending on how many b's there are and where the last one is, that can be a lot more efficient than all those lookaheads. It also generalizes to patterns larger than a character, when the reverse solution would have to reverse the pattern as well, and the rindex wouldn't be able to pattern match at all. All in all, I'd say it's a pretty useful idiom to have in your toolbox.

      Surprised? Really? When the default response to most regular expression questions here seems to be "that is too greedy - insert a ? after the * or +, and then it'll work fine"? We've been burned by too much greediness, so it's really of little surprise that we don't think outside of our boxes ;-)

Re: matching operator question
by Transient (Hermit) on May 13, 2005 at 20:45 UTC
    #!/usr/bin/perl -w use strict; my $string = 'aabbcc'; $string = reverse $string; $string =~ s/b/X/; $string = reverse $string; print $string, "\n"; $string = 'aabbcc'; $string =~ s/b([^b]*)$/X$1/; print $string, "\n"; $string = 'aabbcc'; my $idx; substr( $string, $idx, 1, 'X') if (-1 != ( $idx = rindex( $string, 'b' + ) )); print $string, "\n";
    Update:

    A zero-width positive look-ahead assertion:
    $string =~ s/b(?=[^b]*$)/X/;
Re: matching operator question
by mrborisguy (Hermit) on May 13, 2005 at 20:38 UTC
    $foo = reverse $foo; $foo =~ s/b/X/; $foo = reverse $foo;
    there's probably a more succinct way to write this.

    Update: i haven't tested this (or the other one actually), but something like this might work... find a 'b' that isn't followed by any more 'b's:
    s/b[^b]*$/X/;

    Update:
    After reading Transient's post (below), i realized my second regex is wrong. should be s/b([^b]*)$/X$1/, but now i feel like i just ripped it off from him.
Re: matching operator question
by sh1tn (Priest) on May 13, 2005 at 21:38 UTC
    $foo = 'aabbcc'; $foo =~ s/(.*)$_/$1X/ for qw(a b c); print $foo; STDOUT: aXbXcX


Re: matching operator question
by Grygonos (Chaplain) on May 13, 2005 at 20:54 UTC
    use strict; use warnings; my $string = "aabbcc"; if($string =~ m{^(aabb)(.*)$}) { $string = substr($1,0,length($1)-1) . "X" . $2; } print $string;
    or to be more kloogy..
    print $string = substr($1,0,length($1)-1) . "X" . $2 if $string =~ m{^ +(aabb)(.*)$};
Re: matching operator question
by Animator (Hermit) on May 13, 2005 at 20:56 UTC

    My advice: use rindex and substr (I'm sure you can figure out yourself how to do this). or a look-ahead (code already posted by Transient, in the update).

    Update: I should have taken a better look at Transient's reply... He posted different solutions, including the rindex/substr one...

Re: matching operator question
by tlm (Prior) on May 13, 2005 at 20:39 UTC

    I don't see what makes the second b in your example the "last" matched character. Matched against what? What is the matching criterion you are using?

    Update: BTW, your post suggests that you do not understand the syntax for the s/// operator, which suggests that you don't understand Perl regular expressions at all. I doubt that any answer you get will do you any good until you understand at least the rudiments of regular expressions in Perl. Please read perlretut. You will probably be able to answer your own question then.

    the lowliest monk

      Sorry, I do understand regex's but I screwed up my question - I meant to have said I need a way for $foo =~ s/a/X/ to match the LAST letter a, not the first.

      And now I see that I can just reverse the string first, then reverse it again after the replace.

      I thought maybe there was an option for the s operation that would go in reverse order (like how the g option makes it global in s/a/X/g )

      Thanks for your replies.

        If you're matching a simple string (e.g. 'b'), as opposed to a general regexp pattern, then I think the rindex solution proposed by Transient and Animator is the way to go. For more complicated matches, you could use a negative look-ahead assertion to single out the last occurrence; e.g. to replace the last occurrence of the pattern /[AB]/ with X, you could use

        s/[AB](?!.*[AB])/X/s
        This will replace the last occurrence of A or B with X. The /s modifier is necessary in case the string contains embedded newlines.

        the lowliest monk

Re: matching operator question
by TedPride (Priest) on May 13, 2005 at 22:32 UTC
    $str = 'aabbcc'; $str =~ s/a([^a]*)$/X$1/; print "$str\n"; $str = 'aabbcc'; substr($str, rindex($str, 'a'), 1) = 'X'; print $str;