php's strtr for python

php code
php wiki
php stands for
php abbreviation
php w3schools
php language
php download
php example

php has the strtr function:

strtr('aa-bb-cc', array('aa' => 'bbz', 'bb' => 'x', 'cc' => 'y'));
# bbz-x-y

It replaces dictionary keys in a string with corresponding values and (important) doesn't replace already replaced strings. A naive attempt to write the same in python:

def strtr(strng, replace):
    for s, r in replace.items():
        strng = strng.replace(s, r)
    return strng

strtr('aa-bb-cc', {'aa': 'bbz', 'bb': 'x', 'cc': 'y'})

returns xz-x-y which is not we want (bb got replaced again). How to change the above function so that it behaves like its php counterpart?

(I would prefer an answer without regular expressions, if possible).

Upd: some great answers here. I timed them and found that for short strings Gumbo's version appears to be the fastest, on longer strings the winner is the re solution:

# 'aa-bb-cc'
0.0258 strtr_thg
0.0274 strtr_gumbo
0.0447 strtr_kojiro
0.0701 strtr_aix

# 'aa-bb-cc'*10
0.1474 strtr_aix
0.2261 strtr_thg
0.2366 strtr_gumbo
0.3226 strtr_kojiro

My own version (which is slightly optimized Gumbo's):

def strtr(strng, replace):
    buf, i = [], 0
    while i < len(strng):
        for s, r in replace.items():
            if strng[i:len(s)+i] == s:
                buf.append(r)
                i += len(s)
                break
        else:
            buf.append(strng[i])
            i += 1
    return ''.join(buf)

Complete codes and timings: https://gist.github.com/2889181

Here is a naive algorithm:

Use an index to walk the original string character by character and check for each index whether one of the search strings is equal to the string from the current index on. If a match is found, push the replacement in a buffer and proceed the index by the length of the matched string. If no match is found, proceed the index by one. At the end, concatenate the strings in the buffer to a single string.

def strtr(strng, replace):
    buffer = []
    i, n = 0, len(strng)
    while i < n:
        match = False
        for s, r in replace.items():
            if strng[i:len(s)+i] == s:
                buffer.append(r)
                i = i + len(s)
                match = True
                break
        if not match:
            buffer.append(strng[i])
            i = i + 1
    return ''.join(buffer)

What is PHP? - Manual, The PHP Hypertext Preprocessor (PHP) is a programming language that allows web developers to create dynamic content that interacts with databases. PHP is  PHP is a popular general-purpose scripting language that is especially suited to web development. Fast, flexible and pragmatic, PHP powers everything from your blog to the most popular websites in the world.

The following uses regular expressions to do it:

import re

def strtr(s, repl):
  pattern = '|'.join(map(re.escape, sorted(repl, key=len, reverse=True)))
  return re.sub(pattern, lambda m: repl[m.group()], s)

print(strtr('aa-bb-cc', {'aa': 'bbz', 'bb': 'x', 'cc': 'y'}))

Like the PHP's version, this gives preference to longer matches.

PHP Tutorial, Learn the fundamentals of PHP, one of the most popular languages of modern web development. PHP is a server scripting language, and a powerful tool for making dynamic and interactive Web pages. PHP is a widely-used, free, and efficient alternative to competitors such as Microsoft's ASP. PHP 7 is the latest stable release.

def strtr(strng, replace):
    if replace and strng:
        s, r = replace.popitem()
        return r.join(strtr(subs, dict(replace)) for subs in strng.split(s))
    return strng

j=strtr('aa-bb-cc', {'aa': 'bbz', 'bb': 'x', 'cc': 'y'})
assert j=='bbz-x-y', j

PHP, PHP is a widely used server-side programming language that's become increasingly fast and powerful over the years. PHP works well with HTML and  PHP is a popular general-purpose scripting language that is especially suited to web development.It was originally created by Rasmus Lerdorf in 1994; the PHP reference implementation is now produced by The PHP Group.

str.translate is the equivalent, but can only map to single characters.

PHP Tutorial, An easy-to-read, quick reference for PHP best practices, accepted coding standards, and links to authoritative PHP tutorials around the Web. PHP (recursive acronym for PHP: Hypertext Preprocessor) is a widely-used open source general-purpose scripting language that is especially suited for web development and can be embedded into HTML.

The answers on this thread are so out-dated. Here we go...

Option #1: Use the str.format() function to handle this:
"Hello there {first_name} {last_name}".format(first_name="Bob", last_name="Roy")
Option #2: Use the Template class
from string import Template
t = Template('Hello there $first_name $last_name')
t.substitute(first_name="Bob", last_name="Roy")

Reference: Python String Formatting Best Practices

Learn PHP, learn-php.org is a free interactive PHP tutorial for people who want to learn PHP, fast. PHP Assignment Operators. The PHP assignment operators are used with numeric values to write a value to a variable. The basic assignment operator in PHP is "=". It means that the left operand gets set to the value of the assignment expression on the right.

PHP Courses & Tutorials, PHP is a server side programming language. When a user requests a web page that contains PHP code, the code is processed by the PHP module installed on  Comparison Operators. Comparison operators, as their name implies, allow you to compare two values. You may also be interested in viewing the type comparison tables, as they show examples of various type related comparisons.

PHP: The Right Way, PHP Agency is a national, financial services company started in 2009 with humble origins and a grand vision. Our mission since day one is to change an elitist financial services industry and in so doing, provide EVERYONE access to the best insurance products offered.

Learn PHP, Loosely == equal comparison. If you are using the == operator, or any other comparison operator which uses loosely comparison such as !=, <> or ==, you always have to look at the context to see what, where and why something gets converted to understand what is going on.

Comments
  • We're both missing this (from the strtr doc): The longest keys will be tried first.
  • No, it doesn't give preference to longer matches, it depends on the arbitrary order of dictionary keys: strtr('xxa-bb-cc', { 'xx': 'bbz', 'xxa': 'bby'}) -> 'bbza-bb-cc'. Using sorted(repl.keys(), key=len, reverse=True) in place of repl.keys() should fix that.
  • @Duncan: How surprising, thanks for pointing out (I always thought Python's re gave the longest match, but clearly it doesn't.)
  • Repetitions x*, x+, x? and x{m,n} are all greedy, so they'll repeat x as much as they're allowed and able, x*?, x+?, x??, x{m,n}? are all non-greedy so they match as short as possible. x|y is also non-greedy in the sense that if x matches the engine won't even consider y. That's what happened here: the alternation is tested strictly left to right and stops as soon as it finds a match.
  • @Duncan: That makes sense, thanks for clarifying (I knew about the greedy and non-greedy repetitions, but didn't know about the | operator).
  • Thanks, this solution appears to be the fastest on longer subject strings (see the update).
  • Looks cool, but making 1+2+...+len(repl) recursive calls... I don't know.
  • Hey, you asked for a non-regex version that behaves like php's, you didn't ask for fast. ;) (Besides, I suspect copying the dict is worse than the recursive calls.)
  • @kojiro: fair enough. You definitely win the beauty prize in this thread. Too bad I can't accept multiple answers...
  • BTW, is there any reason why you used ** instead of just dict(replace)?
  • No good reason. The bad reason is that I didn't take time to check that dict(adict) would make a copy, instead of just returning the same dict. – updated answer code.