次のような文字列があるとします。
str = "The &yquick &cbrown &bfox &Yjumps over the &ulazy dog"
文字列内のアンパサンドとそれに続く文字( "&y"や "&c"など)の多くの場所に気づくでしょう。これらの文字を、次のように辞書にある適切な値に置き換える必要があります。
dict = {"&y":"\033[0;30m",
"&c":"\033[0;31m",
"&b":"\033[0;32m",
"&Y":"\033[0;33m",
"&u":"\033[0;34m"}
これを行う最速の方法は何ですか?私は手動ですべてのアンパサンドを見つけ、辞書をループしてそれらを変更することができましたが、それは遅いようです。正規表現の置き換えを行うのも遅いようです(実際のコードには、約30〜40ペアの辞書があります)。
どんな提案もありがたいです、ありがとう。
編集:
この質問を通じてコメントで指摘されているように、私の辞書は実行前に定義され、アプリケーションのライフサイクル中に変更されることはありません。これはANSIエスケープシーケンスのリストであり、約40の項目があります。比較する平均文字列長は約500文字ですが、最大5000文字の文字列もあります(ただし、これらはまれです)。現在Python 2.6も使用しています。
編集#2有効な解決策を提供するだけでなく、ベストではないため、Tor Valamosの回答を正しいものとして受け入れました(== --- ==)best解決策)、しかし、他のすべてを考慮に入れ、それらすべてを比較するために膨大な量の作業を行いました。その答えは、StackOverflowでこれまでに出会った中で最も優れた、最も役立つ答えの1つです。あなたへの称賛。
mydict = {"&y":"\033[0;30m",
"&c":"\033[0;31m",
"&b":"\033[0;32m",
"&Y":"\033[0;33m",
"&u":"\033[0;34m"}
mystr = "The &yquick &cbrown &bfox &Yjumps over the &ulazy dog"
for k, v in mydict.iteritems():
mystr = mystr.replace(k, v)
print mystr
The ←[0;30mquick ←[0;31mbrown ←[0;32mfox ←[0;33mjumps over the ←[0;34mlazy dog
私はいくつかの解決策を自由に比較しました:
mydict = dict([('&' + chr(i), str(i)) for i in list(range(65, 91)) + list(range(97, 123))])
# random inserts between keys
from random import randint
rawstr = ''.join(mydict.keys())
mystr = ''
for i in range(0, len(rawstr), 2):
mystr += chr(randint(65,91)) * randint(0,20) # insert between 0 and 20 chars
from time import time
# How many times to run each solution
rep = 10000
print 'Running %d times with string length %d and ' \
'random inserts of lengths 0-20' % (rep, len(mystr))
# My solution
t = time()
for x in range(rep):
for k, v in mydict.items():
mystr.replace(k, v)
#print(mystr)
print '%-30s' % 'Tor fixed & variable dict', time()-t
from re import sub, compile, escape
# Peter Hansen
t = time()
for x in range(rep):
sub(r'(&[a-zA-Z])', r'%(\1)s', mystr) % mydict
print '%-30s' % 'Peter fixed & variable dict', time()-t
# Claudiu
def multiple_replace(dict, text):
# Create a regular expression from the dictionary keys
regex = compile("(%s)" % "|".join(map(escape, dict.keys())))
# For each match, look-up corresponding value in dictionary
return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
t = time()
for x in range(rep):
multiple_replace(mydict, mystr)
print '%-30s' % 'Claudio variable dict', time()-t
# Claudiu - Precompiled
regex = compile("(%s)" % "|".join(map(escape, mydict.keys())))
t = time()
for x in range(rep):
regex.sub(lambda mo: mydict[mo.string[mo.start():mo.end()]], mystr)
print '%-30s' % 'Claudio fixed dict', time()-t
# Andrew Y - variable dict
def mysubst(somestr, somedict):
subs = somestr.split("&")
return subs[0] + "".join(map(lambda arg: somedict["&" + arg[0:1]] + arg[1:], subs[1:]))
t = time()
for x in range(rep):
mysubst(mystr, mydict)
print '%-30s' % 'Andrew Y variable dict', time()-t
# Andrew Y - fixed
def repl(s):
return mydict["&"+s[0:1]] + s[1:]
t = time()
for x in range(rep):
subs = mystr.split("&")
res = subs[0] + "".join(map(repl, subs[1:]))
print '%-30s' % 'Andrew Y fixed dict', time()-t
結果Python 2.6
Running 10000 times with string length 490 and random inserts of lengths 0-20
Tor fixed & variable dict 1.04699993134
Peter fixed & variable dict 0.218999862671
Claudio variable dict 2.48400020599
Claudio fixed dict 0.0940001010895
Andrew Y variable dict 0.0309998989105
Andrew Y fixed dict 0.0310001373291
Claudiuとandrewの両方のソリューションが0になり続けたため、10000ランに増やす必要がありました。
私はそれをPython(Unicodeのため)で実行し、39から1024までの文字を置き換えました(38はアンパサンドなので、含めたくありませんでした)。文字列の長さは最大10.000で、0〜20の可変ランダム挿入による約980の置換を含みます。 39〜1024のUnicode値は、1バイトと2バイトの両方の長さの文字を引き起こし、一部のソリューションに影響を与える可能性があります。
mydict = dict([('&' + chr(i), str(i)) for i in range(39,1024)])
# random inserts between keys
from random import randint
rawstr = ''.join(mydict.keys())
mystr = ''
for i in range(0, len(rawstr), 2):
mystr += chr(randint(65,91)) * randint(0,20) # insert between 0 and 20 chars
from time import time
# How many times to run each solution
rep = 10000
print('Running %d times with string length %d and ' \
'random inserts of lengths 0-20' % (rep, len(mystr)))
# Tor Valamo - too long
#t = time()
#for x in range(rep):
# for k, v in mydict.items():
# mystr.replace(k, v)
#print('%-30s' % 'Tor fixed & variable dict', time()-t)
from re import sub, compile, escape
# Peter Hansen
t = time()
for x in range(rep):
sub(r'(&[a-zA-Z])', r'%(\1)s', mystr) % mydict
print('%-30s' % 'Peter fixed & variable dict', time()-t)
# Peter 2
def dictsub(m):
return mydict[m.group()]
t = time()
for x in range(rep):
sub(r'(&[a-zA-Z])', dictsub, mystr)
print('%-30s' % 'Peter fixed dict', time()-t)
# Claudiu - too long
#def multiple_replace(dict, text):
# # Create a regular expression from the dictionary keys
# regex = compile("(%s)" % "|".join(map(escape, dict.keys())))
#
# # For each match, look-up corresponding value in dictionary
# return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
#
#t = time()
#for x in range(rep):
# multiple_replace(mydict, mystr)
#print('%-30s' % 'Claudio variable dict', time()-t)
# Claudiu - Precompiled
regex = compile("(%s)" % "|".join(map(escape, mydict.keys())))
t = time()
for x in range(rep):
regex.sub(lambda mo: mydict[mo.string[mo.start():mo.end()]], mystr)
print('%-30s' % 'Claudio fixed dict', time()-t)
# Separate setup for Andrew and gnibbler optimized dict
mydict = dict((k[1], v) for k, v in mydict.items())
# Andrew Y - variable dict
def mysubst(somestr, somedict):
subs = somestr.split("&")
return subs[0] + "".join(map(lambda arg: somedict[arg[0:1]] + arg[1:], subs[1:]))
def mysubst2(somestr, somedict):
subs = somestr.split("&")
return subs[0].join(map(lambda arg: somedict[arg[0:1]] + arg[1:], subs[1:]))
t = time()
for x in range(rep):
mysubst(mystr, mydict)
print('%-30s' % 'Andrew Y variable dict', time()-t)
t = time()
for x in range(rep):
mysubst2(mystr, mydict)
print('%-30s' % 'Andrew Y variable dict 2', time()-t)
# Andrew Y - fixed
def repl(s):
return mydict[s[0:1]] + s[1:]
t = time()
for x in range(rep):
subs = mystr.split("&")
res = subs[0] + "".join(map(repl, subs[1:]))
print('%-30s' % 'Andrew Y fixed dict', time()-t)
# gnibbler
t = time()
for x in range(rep):
myparts = mystr.split("&")
myparts[1:]=[mydict[x[0]]+x[1:] for x in myparts[1:]]
"".join(myparts)
print('%-30s' % 'gnibbler fixed & variable dict', time()-t)
結果:
Running 10000 times with string length 9491 and random inserts of lengths 0-20
Tor fixed & variable dict 0.0 # disqualified 329 secs
Peter fixed & variable dict 2.07799983025
Peter fixed dict 1.53100013733
Claudio variable dict 0.0 # disqualified, 37 secs
Claudio fixed dict 1.5
Andrew Y variable dict 0.578000068665
Andrew Y variable dict 2 0.56299996376
Andrew Y fixed dict 0.56200003624
gnibbler fixed & variable dict 0.530999898911
(** gnibblerのコードは、キーに「&」が含まれていない別のdictを使用していることに注意してください。Andrewのコードもこの代替dictを使用していますが、それほど大きな違いはなく、たぶん0.01倍のスピードアップです。)
正規表現の置換と標準の文字列フォーマットを利用して、これを試してください:
_# using your stated values for str and dict:
>>> import re
>>> str = re.sub(r'(&[a-zA-Z])', r'%(\1)s', str)
>>> str % dict
'The \x1b[0;30mquick \x1b[0;31mbrown \x1b[0;32mfox \x1b[0;33mjumps over the \x1b[0;34mlazy dog'
_
Re.sub()呼び出しは、アンパサンドとそれに続く1文字のすべてのシーケンスを、同じパターンを含むパターン%(..)sで置き換えます。
%フォーマットは、より一般的な位置引数ではなく、辞書を使用して置換を指定できる文字列フォーマットの機能を利用します。
代替手段は、コールバックを使用してre.subでこれを直接実行できます。
_>>> import re
>>> def dictsub(m):
>>> return dict[m.group()]
>>> str = re.sub(r'(&[a-zA-Z])', dictsub, str)
_
今回は、クロージャーを使用して、コールバック関数内から辞書を参照しています。このアプローチにより、もう少し柔軟性が得られます。たとえば、dict.get(m.group(), '??')
のようなものを使用して、認識されないコードシーケンスを持つ文字列がある場合に例外が発生するのを回避できます。
(ちなみに、 "dict"と "str"はどちらも組み込み関数であり、自分のコードでこれらの名前を頻繁に使用すると問題が発生します。知らない場合に備えて、これらは問題ありません。もちろん、このような質問です。)
編集: Torのテストコードを確認することにしましたが、これは代表的なものではなく、実際にはバグがあると結論付けました。生成された文字列には、アンパサンド(!)さえ含まれていません。以下の改訂されたコードは、OPの入力例と同様に、代表的な辞書と文字列を生成します。
また、各アルゴリズムの出力が同じであることを確認したいと思いました。以下は、Tor、Cine、およびClaudiuのコードのみを含む、変更されたテストプログラムです。他のユーザーがサンプル入力を壊していたためです。 (辞書が基本的にall可能なアンパサンドシーケンスをマップしない限り、それらはすべて壊れやすいと思います。これは、Torのテストコードが行っていたものです。)これは、ランダムに適切にシードします。数値ジェネレーターなので、各実行は同じです。最後に、パフォーマンスのマイナーな改善のために、関数呼び出しのオーバーヘッドを回避するジェネレーターを使用してマイナーなバリエーションを追加しました。
_from time import time
import string
import random
import re
random.seed(1919096) # ensure consistent runs
# build dictionary with 40 mappings, representative of original question
mydict = dict(('&' + random.choice(string.letters), '\x1b[0;%sm' % (30+i)) for i in range(40))
# build simulated input, with mix of text, spaces, ampersands in reasonable proportions
letters = string.letters + ' ' * 12 + '&' * 6
mystr = ''.join(random.choice(letters) for i in range(1000))
# How many times to run each solution
rep = 10000
print('Running %d times with string length %d and %d ampersands'
% (rep, len(mystr), mystr.count('&')))
# Tor Valamo
# fixed from Tor's test, so it actually builds up the final string properly
t = time()
for x in range(rep):
output = mystr
for k, v in mydict.items():
output = output.replace(k, v)
print('%-30s' % 'Tor fixed & variable dict', time() - t)
# capture "known good" output as expected, to verify others
expected = output
# Peter Hansen
# build charset to use in regex for safe dict lookup
charset = ''.join(x[1] for x in mydict.keys())
# grab reference to method on regex, for speed
patsub = re.compile(r'(&[%s])' % charset).sub
t = time()
for x in range(rep):
output = patsub(r'%(\1)s', mystr) % mydict
print('%-30s' % 'Peter fixed & variable dict', time()-t)
assert output == expected
# Peter 2
def dictsub(m):
return mydict[m.group()]
t = time()
for x in range(rep):
output = patsub(dictsub, mystr)
print('%-30s' % 'Peter fixed dict', time() - t)
assert output == expected
# Peter 3 - freaky generator version, to avoid function call overhead
def dictsub(d):
m = yield None
while 1:
m = yield d[m.group()]
dictsub = dictsub(mydict).send
dictsub(None) # "prime" it
t = time()
for x in range(rep):
output = patsub(dictsub, mystr)
print('%-30s' % 'Peter generator', time() - t)
assert output == expected
# Claudiu - Precompiled
regex_sub = re.compile("(%s)" % "|".join(mydict.keys())).sub
t = time()
for x in range(rep):
output = regex_sub(lambda mo: mydict[mo.string[mo.start():mo.end()]], mystr)
print('%-30s' % 'Claudio fixed dict', time() - t)
assert output == expected
_
前にベンチマーク結果を含めるのを忘れていました。
[。 。]( 'Peter fixed dict'、1.0920000076293945) ( 'Peter generator'、1.0460000038146973) ( 'Claudio fixed dict'、1.562000036239624)
また、入力のスニペットと正しい出力:
_mystr = 'lTEQDMAPvksk k&z Txp vrnhQ GHaO&GNFY&&a...'
mydict = {'&p': '\x1b[0;37m', '&q': '\x1b[0;66m', '&v': ...}
output = 'lTEQDMAPvksk k←[0;57m Txp vrnhQ GHaO←[0;67mNFY&&a P...'
_
Torのテストコード出力から見たものと比較すると:
_mystr = 'VVVVVVVPPPPPPPPPPPPPPPXXXXXXXXYYYFFFFFFFFFFFFEEEEEEEEEEE...'
mydict = {'&p': '112', '&q': '113', '&r': '114', '&s': '115', ...}
output = # same as mystr since there were no ampersands inside
_
あなたが本当にトピックを掘り下げたいなら、これを見てください: http://en.wikipedia.org/wiki/Aho-Corasick_algorithm
辞書を反復して文字列の各要素を置き換えることによる明白な解決策は、O(n*m)
時間かかります。ここで、nは辞書のサイズ、mは文字列の長さです。
一方、Aho-Corasick-AlgorithmはO(n+m+f)
で辞書のすべてのエントリを検索します。ここで、fは検索された要素の数です。
リスト内のキーの数が多く、文字列内の出現数が少ない(そしてほとんどがゼロ)場合は、文字列内のアンパサンドの出現を繰り返し、最初のキーで指定された辞書を使用できます。部分文字列の文字。私はpythonで頻繁にコーディングしないので、スタイルが少しずれているかもしれませんが、ここに私の見解があります:
str = "The &yquick &cbrown &bfox &Yjumps over the &ulazy dog"
dict = {"&y":"\033[0;30m",
"&c":"\033[0;31m",
"&b":"\033[0;32m",
"&Y":"\033[0;33m",
"&u":"\033[0;34m"}
def rep(s):
return dict["&"+s[0:1]] + s[1:]
subs = str.split("&")
res = subs[0] + "".join(map(rep, subs[1:]))
print res
もちろん、文字列自体からアンパサンドがある場合に何が起こるかという問題があります。このプロセスを通過する前にアンパサンドを何らかの方法でエスケープし、その後このプロセスをアンエスケープする必要があります。
もちろん、パフォーマンスの問題でよくあることですが、一般的な(そして最悪の場合の)データセットでさまざまなアプローチのタイミングを調整し、それらを比較することは良いことです。
編集:それを別の関数に入れて、任意の辞書を操作します:
def mysubst(somestr, somedict):
subs = somestr.split("&")
return subs[0] + "".join(map(lambda arg: somedict["&" + arg[0:1]] + arg[1:], subs[1:]))
EDIT2:不要な連結を取り除きます。多くの繰り返しで、以前の連結よりも少し速いようです。
def mysubst(somestr, somedict):
subs = somestr.split("&")
return subs[0].join(map(lambda arg: somedict["&" + arg[0:1]] + arg[1:], subs[1:]))
これがPythonのC拡張アプローチです
const char *dvals[]={
//"0-64
"","","","","","","","","","",
"","","","","","","","","","",
"","","","","","","","","","",
"","","","","","","","","","",
"","","","","","","","","","",
"","","","","","","","","","",
"","","","","",
//A-Z
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","33",
"",
//
"","","","","","",
//a-z
"","32","31","","",
"","","","","",
"","","","","",
"","","","","",
"34","","","","30",
""
};
int dsub(char*d,char*s){
char *ofs=d;
do{
if(*s=='&' && s[1]<='z' && *dvals[s[1]]){
//\033[0;
*d++='\\',*d++='0',*d++='3',*d++='3',*d++='[',*d++='0',*d++=';';
//consider as fixed 2 digits
*d++=dvals[s[1]][0];
*d++=dvals[s[1]][1];
*d++='m';
s++; //skip
//non &,invalid, unused (&) ampersand sequences will go here.
}else *d++=*s;
}while(*s++);
return d-ofs-1;
}
テストしたPythonコード
from mylib import *
import time
start=time.time()
instr="The &yquick &cbrown &bfox &Yjumps over the &ulazy dog, skip &Unknown.\n"*100000
x=dsub(instr)
end=time.time()
print "time taken",end-start,",input str length",len(x)
print "first few lines"
print x[:1100]
結果
time taken 0.140000104904 ,input str length 11000000
first few lines
The \033[0;30mquick \033[0;31mbrown \033[0;32mfox \033[0;33mjumps over the \033[0;34mlazy dog, skip &Unknown.
The \033[0;30mquick \033[0;31mbrown \033[0;32mfox \033[0;33mjumps over the \033[0;34mlazy dog, skip &Unknown.
The \033[0;30mquick \033[0;31mbrown \033[0;32mfox \033[0;33mjumps over the \033[0;34mlazy dog, skip &Unknown.
The \033[0;30mquick \033[0;31mbrown \033[0;32mfox \033[0;33mjumps over the \033[0;34mlazy dog, skip &Unknown.
The \033[0;30mquick \033[0;31mbrown \033[0;32mfox \033[0;33mjumps over the \033[0;34mlazy dog, skip &Unknown.
The \033[0;30mquick \033[0;31mbrown \033[0;32mfox \033[0;33mjumps over the \033[0;34mlazy dog, skip &Unknown.
The \033[0;30mquick \033[0;31mbrown \033[0;32mfox \033[0;33mjumps over the \033[0;34mlazy dog, skip &Unknown.
The \033[0;30mquick \033[0;31mbrown \033[0;32mfox \033[0;33mjumps over the \033[0;34mlazy dog, skip &Unknown.
The \033[0;30mquick \033[0;31mbrown \033[0;32mfox \033[0;33mjumps over the \033[0;34mlazy dog, skip &Unknown.
The \033[0;30mquick \033[0;31mbrown \033[0;32mfox \033[0;33mjumps over the \033[0;34mlazy dog, skip &Unknown.
O(n)で実行できると仮定し、My Mobile Celeron 1.6では160 ms(avg)11 MBの文字列のみを使用GHz PC
また、不明な文字をそのままスキップします。たとえば、&Unknown
はそのまま返されます
コンパイルやバグなどで何か問題があれば教えてください...
これ それはあなたが望むことをするようです-RegExpsを使用して複数の文字列を一度に置き換えます。関連するコードは次のとおりです。
def multiple_replace(dict, text):
# Create a regular expression from the dictionary keys
regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
# For each match, look-up corresponding value in dictionary
return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
print multiple_replace(dict, str)
置換規則を定義する一般的な解決策は、マップを提供する関数を使用して正規表現置換を使用することです( re.sub() を参照)。
import re
str = "The &yquick &cbrown &bfox &Yjumps over the &ulazy dog"
dict = {"&y":"\033[0;30m",
"&c":"\033[0;31m",
"&b":"\033[0;32m",
"&Y":"\033[0;33m",
"&u":"\033[0;34m"}
def programmaticReplacement( match ):
return dict[ match.group( 1 ) ]
colorstring = re.sub( '(\&.)', programmaticReplacement, str )
これは、自明ではない置換(たとえば、置換を作成するために算術演算が必要なもの)には特に適しています。
これは分割/結合を使用したバージョンです
mydict = {"y":"\033[0;30m",
"c":"\033[0;31m",
"b":"\033[0;32m",
"Y":"\033[0;33m",
"u":"\033[0;34m"}
mystr = "The &yquick &cbrown &bfox &Yjumps over the &ulazy dog"
myparts = mystr.split("&")
myparts[1:]=[mydict[x[0]]+x[1:] for x in myparts[1:]]
print "".join(myparts)
無効なコードのアンパサンドがある場合は、これを使用してそれらを保持できます。
myparts[1:]=[mydict.get(x[0],"&"+x[0])+x[1:] for x in myparts[1:]]
ピーターハンセンは、アンパサンドが2つある場合、これは失敗することを指摘しました。その場合、このバージョンを使用してください
mystr = "The &yquick &cbrown &bfox &Yjumps over the &&ulazy dog"
myparts = mystr.split("&")
myparts[1:]=[mydict.get(x[:1],"&"+x[:1])+x[1:] for x in myparts[1:]]
print "".join(myparts)
誰かが単純なパーサーを使用することについて言及したので、私はpyparsingを使用してそれを作り上げると思いました。 pyparsingのtransformStringメソッドを使用することにより、pyparsingはソース文字列を内部的にスキャンし、一致するテキストと介在するテキストのリストを作成します。すべてが完了すると、transformStringが '' .joinのthisリストになるため、増分で文字列を作成してもパフォーマンスの問題はありません。 (ANSIreplacerに定義された解析アクションは、一致した&_文字から目的のエスケープシーケンスへの変換を行い、一致したテキストを解析アクションの出力で置き換えます。一致するシーケンスのみがパーサー式を満たすため、未定義の&_シーケンスを処理する解析アクション。)
FollowedBy( '&')は厳密には必要ありませんが、すべてのマークアップオプションのより高価なチェックを行う前に、パーサーが実際にアンパサンドに配置されていることを確認することで、解析プロセスを短縮します。
from pyparsing import FollowedBy, oneOf
escLookup = {"&y":"\033[0;30m",
"&c":"\033[0;31m",
"&b":"\033[0;32m",
"&Y":"\033[0;33m",
"&u":"\033[0;34m"}
# make a single expression that will look for a leading '&', then try to
# match each of the escape expressions
ANSIreplacer = FollowedBy('&') + oneOf(escLookup.keys())
# add a parse action that will replace the matched text with the
# corresponding ANSI sequence
ANSIreplacer.setParseAction(lambda toks: escLookup[toks[0]])
# now use the replacer to transform the test string; throw in some extra
# ampersands to show what happens with non-matching sequences
src = "The &yquick &cbrown &bfox &Yjumps over the &ulazy dog & &Zjumps back"
out = ANSIreplacer.transformString(src)
print repr(out)
プリント:
'The \x1b[0;30mquick \x1b[0;31mbrown \x1b[0;32mfox \x1b[0;33mjumps over
the \x1b[0;34mlazy dog & &Zjumps back'
これは確かにパフォーマンスコンテストに勝つことはできませんが、マークアップがより複雑になり始めた場合は、パーサーの基盤があると拡張が容易になります。
このソリューションの速度についても不明ですが、辞書をループして組み込みを繰り返し呼び出すことができます
str.replace(old, new)
これは、元の文字列が長すぎない場合は適切に動作する可能性がありますが、文字列が長くなると明らかに影響を受けます。
Python=でこの一括置換を行う場合の問題は、文字列の不変性です。文字列内の1つのアイテムを置換するたびに、新しい文字列全体がヒープから何度も再割り当てされます。
したがって、最速のソリューションが必要な場合は、可変コンテナ(リストなど)を使用するか、この機構をプレーンなC(またはPyrexまたはCythonでより優れたもの)で記述する必要があります。いずれにせよ、私は単純な有限状態マシンに基づく単純なパーサーを作成し、文字列のシンボルを1つずつフィードすることをお勧めします。
正規表現はfsmを使用してバックグラウンドで動作するため、同様の方法で動作する正規表現に基づく推奨されるソリューション。
>>> a=[]
>>> str = "The &yquick &cbrown &bfox &Yjumps over the &ulazy dog"
>>> d={"&y":"\033[0;30m",
... "&c":"\033[0;31m",
... "&b":"\033[0;32m",
... "&Y":"\033[0;33m",
... "&u":"\033[0;34m"}
>>> for item in str.split():
... if item[:2] in d:
... a.append(d[item[:2]]+item[2:])
... else: a.append(item)
>>> print ' '.join(a)
これを試して
tr.replace( "&y"、dict ["&y"])
tr.replace( "&c"、dict ["&c"])
tr.replace( "&b"、dict ["&b"])
tr.replace( "&Y"、dict ["&Y"])
tr.replace( "&u"、dict ["&u"])