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

Hi perlmonks,

I've been successfully (?) getting into Perl but now I'm a little stuck here.

Many times I found great solutions from you guys so I decided to register and make a thread myself now.


Say I got a string 'x x x a x x x b x x x a x x x b x x x' and want to replace all 'a's with a 'b' and all 'b's with an 'a'.

What's the best way of achieving this without making it replace a previously replaced 'a' (that now is a 'b') with an 'a'?


Here are my several ways I came up with, one of them fails due to the issue described above:



#!/usr/bin/env perl use strict; use warnings; my $test = 'x x x a x x x b x x x a x x x b x x x'; print "ORIGINAL: $test\n\n"; # 1 - array my @array = split(/ /, $test); for (@array) { if ($_ eq 'a') { $_ = 'b'; } elsif ($_ eq 'b') { $_ = 'a'; } } print "ARRAY: @array\n"; # 2 - s/// (FAILS) my $s = $test; $s =~ s/a/b/g; $s =~ s/b/a/g; print "s///: $s\n"; # 3 - s/// map my $map = join(' ', map{ if (/a/) { s/a/b/; } elsif (/b/) { s/b/a/; } +$_ } split(/ /, $test)); print "s/// MAP: $map\n"; # 4 - substr my $substr = $test; for (my $i = 0; $i <= length($substr); $i++) { if (substr($substr, $i, 1) eq 'a') { substr($substr, $i, 1) = 'b'; +} elsif (substr($substr, $i, 1) eq 'b') { substr($substr, $i, 1) = 'a +'; } } print "SUBSTR: $substr\n";



Output:
ORIGINAL: x x x a x x x b x x x a x x x b x x x

ARRAY: x x x b x x x a x x x b x x x a x x x
s///: x x x a x x x a x x x a x x x a x x x
s/// MAP: x x x b x x x a x x x b x x x a x x x
SUBSTR: x x x b x x x a x x x b x x x a x x x


Is there any way to achieve this with a single s/// operation?


Thanks for any suggestions that will all be greatly appreciated!
  • Comment on Multiple replacements with the exception of previously made replacements.
  • Download Code

Replies are listed 'Best First'.
Re: Multiple replacements with the exception of previously made replacements.
by BrowserUk (Patriarch) on Aug 17, 2009 at 13:38 UTC
    Is there any way to achieve this with a single s/// operation?

    How about with zero s/// operations? (Use tr/// instead :)

    [0] Perl> $s = 'x x x a x x x b x x x a x x x b x x x';; [0] Perl> $s =~ tr[ab][ba];; [0] Perl> print $s;; x x x b x x x a x x x b x x x a x x x

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Multiple replacements with the exception of previously made replacements.
by SuicideJunkie (Vicar) on Aug 17, 2009 at 13:38 UTC
    Use tr/ab/ba/ instead of s///?

    This is presuming that your elements to match are single characters as in the example, however.

      Wow, I'm impressed by how fast I got three awesome replies here, thanks to all of you so far.

      "This is presuming that your elements to match are single characters as in the example, however."

      Argh, I'm really sorry, I should've picked a better example here, I forgot about tr/// when choosing the 'a' and 'b' one.

      In my real life issue I want to replace
      every \bi\b with a 'you',
      every \byou\b with an 'i',
      every \bu\b with an 'i',
      every \byour\n with a 'my',
      every \bmy\b with a 'your',
      every \bam\b with an 'are',
      every \bare\b with an 'am',
      and every \br\b with an 'am'.

      "i think you really should've picked a better example for your perlmonks thread"
      =>
      "you think i really should've picked a better example for my perlmonks thread"



      I'm really sorry for the poor example in my inital post.
      Thanks guys.
        How about this:
        1. Create a hash which describes the transformations (i.e. $translate{'are'}='am' etc.)
        2. Use split to split your sentence into words.
        3. Use map to apply the translation of the words, using your %translate hash.
        4. Use join to put back the words into a sentence

        -- 
        Ronald Fischer <ynnor@mm.st>
        ...in which case, JavaFans excellent answer (I wish I'd have thought of it:-) is, IMO, the way to go.

        A user level that continues to overstate my experience :-))
Re: Multiple replacements with the exception of previously made replacements.
by JavaFan (Canon) on Aug 17, 2009 at 14:03 UTC
    Here's with s///, just in case your 'a' and 'b' are generic, and could actually consist of longer strings:
    my %replace = ("a" => "b", "b" => "a"); s/(a|b)/$replace{$1}/g;
      Hi guys,

      I'm still impressed by how fast you get great replies here, I knew I could count on you.

      rovf and JavaFan:
      Awesome, I wouldn't have been able to think of that solution.

      Originally I was wondering about a way to do it with a single s/// operation but without any helper variable,
      but this is a great way and it allows me to leave the s/// and edit the hash's keys and values the way I like.

      Thanks a lot! :)
        If you want to leave the s/// while be able to add hash elements, write it like this:
        my %replace = (....); my $pat = join '|', map {quotemeta} keys %replace; s/($pat)/$replace{$1}/g;
        Otherwise, you'll have to update the pattern in the s/// if you add a new hash element.
Re: Multiple replacements with the exception of previously made replacements.
by Bloodnok (Vicar) on Aug 17, 2009 at 13:40 UTC
    Why not use the tr/y operator - described (here)?
    use warnings; use strict; my $test = 'x x x a x x x b x x x a x x x b x x x'; warn $test; (my $res = $test) =~ y/[ab]/[ba]/; warn $res;
    $ perl tst.pl x x x a x x x b x x x a x x x b x x x at tst.pl line 5. x x x b x x x a x x x b x x x a x x x at tst.pl line 8.
    A user level that continues to overstate my experience :-))
Re: Multiple replacements with the exception of previously made replacements.
by sanku (Beadle) on Aug 25, 2009 at 07:37 UTC
    hi, The following regular expression will work
    my $test = 'x x x a x x x b x x x a x x x b x x x'; $test =~ tr/[ab]/[ba]/; print "$test";:w