php's strtr for python

Question:

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

Asked By: georg

||

Answers:

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

Answered By: phihag

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)
Answered By: Gumbo

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.

Answered By: NPE
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
Answered By: kojiro

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

Answered By: Basil Musa

the answers show that none of them are correct. The author confirms this in the comments and puts "I accept".
Is this problem still not solved?
And my friend above me generally advertises his parser-translator-affiliate and have no banned

Answered By: allvolload
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.