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

I'm aware that i can replace, using a regex, a whole bunch of characters with their equivalent characters using tr///; ie: tr/abcdefghijklmnopqrstuvwxyz/zyxwvutsrqponmlkjihgfedcba/; which would basically reverse the alphabet. in making a program that takes a simple phrase, and changes it into l337 words, i came across a problem. I can only replace a character with ONE character. This would work:
$text = 'abcdefghijklmnopqrstuvwxyz'; $text =~ tr/abc/@b©/; print $text;
but this won't work:
$text = 'abcdefghijklmnopqrstuvwxyz'; $text =~ tr^m,n^|\/|,/\/^; print $text;
i thought the comma's would help, but they didn't. Any help?

Replies are listed 'Best First'.
Re (tilly) 1: Replace all at once
by tilly (Archbishop) on Dec 08, 2001 at 23:12 UTC
    You need to use the s/// operator to get more flexible replacement. Here is a simple snippet using the technique to get you started:
    $string = "We modify foos and bars but not foobars.\n"; my %trans = qw( foo bar bar foo foobar foobar ); # reverse sort is a hack to put strings before # substrings. (ie foobar before foo) my $re_str = join "|", map quotemeta($_), reverse sort keys %trans; $string =~ s/($re_str)/$trans{$1}/g; print $string;
    The line where we build up $re_str is a bit tricky if you haven't seen that kind of thing before. The first trick is that you need to read it from right to left. Here are the steps:
    1. Call keys on the translation hash to get a list of strings you want to find.
    2. sort and reverse to put them in reverse sorted order. As the comment says, this is a hack. If your RE had foo appear before foobar, then it would never match "foobar", it would always stop when it found that it could match "foo". (If this comment makes no sense to you, then pick up Mastering Regular Expressions. Or pick up japhy's book when it comes out.)
    3. You then map them through quotemeta. What map does is allows you to transform a list. The transformation that we want in this case is from strings that might contain characters which are meaningful to regular expressions (eg "|") and we want to escape them. Which is what quotemeta does for us.
    4. Then join with a "|". This gives us a string like "foobar|foo|bar" which will now match exactly the keys of our original hash.
    And then we do a search and replace. The pattern will match any of our keys. The replacement replaces the hash lookup.

    If this answer totally confuses you from start to finish (which I fear it may), then I recommend picking up merlyn's book, Learning Perl. It should (among other things) give you a fairly digestible overview of what regular expressions are, how to read them, and how to produce them. After you have a handle on regular expressions, the above explanation is likely to make more sense.

    UPDATE
    In response to japhy, I don't mind. :-)

      Here's an excerpt from chapter 7 (substitution):
      Example 7.4 Sorting text by length for use in a regex.
      $keys = join '|', # 4 map { quotemeta } # 3 sort { length($b) <=> length($a) } # 2 keys %change; # 1
      1. We get the list of keys from the hash – these are the strings...
      2. We sort the keys by their length, so that the longest strings come first...
      3. We run them through the quotemeta() function to make them safe for regexes...
      4. And then we join the regex-safe strings with "|" in between them, for regex alternation.
      Doing a benchmark, I see that the reverse sort LIST method is (as one would imagine) faster than the sort { length($b) <=> length($a) } LIST method (by more than two times). I'll incorporate your code, tilly, if'n you don't mind. :)

      _____________________________________________________
      Jeff[japhy]Pinyan: Perl, regex, and perl hacker.
      s++=END;++y(;-P)}y js++=;shajsj<++y(p-q)}?print:??;

        Why didn't you orcish the sort { length($b) <=> length ($a) }?

        ------
        We are the carpenters and bricklayers of the Information Age.

        Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

Re: Replace all at once
by Beatnik (Parson) on Dec 08, 2001 at 22:55 UTC
    s/// to the rescue ! The s/// POD (should explain)explains how you can use it to substitute chars.
    $foo = "I love beer ! I really love it"; $foo =~ s/love/adore/g; #This will swap love with adore, every occuran +ce #The g is for global.

    Greetz
    Beatnik
    ... Quidquid perl dictum sit, altum viditur.
Re: Replace all at once
by dws (Chancellor) on Dec 08, 2001 at 22:55 UTC
    I'm aware that i can replace, using a regex, a whole bunch of characters with their equivalent characters using tr///;

    First, tr is not a regular expression. The two bear a passing resemblance due to syntax, but they are very different beasts.

    Now in your example:   $text =~ tr^m,n^|\/|,/\/^; there are more charactes on the right than there are on the left.