web-dev-qa-db-fra.com

Chaîne de masse remplacer en python?

Dites que j'ai une chaîne qui ressemble à ceci:

str = "The &yquick &cbrown &bfox &Yjumps over the &ulazy dog"

Vous remarquerez de nombreux emplacements dans la chaîne contenant une esperluette, suivis d'un caractère (tels que "& y" et "& c"). Je dois remplacer ces caractères par une valeur appropriée que j'ai dans un dictionnaire, comme ceci:

dict = {"&y":"\033[0;30m",
        "&c":"\033[0;31m",
        "&b":"\033[0;32m",
        "&Y":"\033[0;33m",
        "&u":"\033[0;34m"}

Quel est le moyen le plus rapide de faire cela? Je pouvais trouver manuellement tous les esperluettes, puis parcourir le dictionnaire pour les changer, mais cela semble lent. Faire un tas de regex remplace semble aussi lent (je vais avoir un dictionnaire d'environ 30-40 paires dans mon code actuel).

Toutes les suggestions sont appréciées, merci.

Modifier:

Comme il a été souligné dans les commentaires au cours de cette question, mon dictionnaire est défini avant l'exécution et ne changera jamais au cours du cycle de vie des applications. Il s'agit d'une liste de séquences d'échappement ANSI contenant environ 40 éléments. Ma longueur moyenne de chaîne à comparer sera d’environ 500 caractères, mais il y en aura jusqu’à 5000 caractères (bien que ceux-ci soient rares). J'utilise également Python 2.6 actuellement.

Edit # 2 J'ai accepté la réponse de Tor Valamos comme étant la bonne, car elle donnait non seulement une solution valable (bien que ce ne fût pas la meilleure solution), mais elle prenait en compte toutes les autres. une énorme quantité de travail pour les comparer tous. Cette réponse est l'une des meilleures et des plus utiles que j'ai jamais eues sur StackOverflow. Bravo à vous.

42
Mike Trpcic
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

J'ai pris la liberté de comparer quelques solutions:

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

Résultats en 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

Les solutions de claudiu et andrew continuaient à aller en 0, je devais donc l'augmenter à 10 000 points.

Je l'ai lancé dans Python 3 (à cause de l'unicode) avec des remplacements de caractères de 39 à 1024 (38 étant l'esperluette, je ne voulais donc pas l'inclure). Longueur de chaîne allant jusqu'à 10 000, y compris environ 980 remplacements avec des insertions aléatoires variables de longueur 0-20. Les valeurs unicode comprises entre 39 et 1024 entraînent des caractères de 1 et 2 octets de longueur, ce qui peut affecter certaines solutions.

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)

Résultats:

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

(** Notez que le code de gnibbler utilise un dict différent, où les clés n'ont pas le '&' inclus. Le code d'Andrew utilise également ce dict alternatif, mais cela ne faisait pas beaucoup de différence, peut-être juste une accélération de 0.01x.)

30
Tor Valamo

Essayez ceci en utilisant la substitution d’expression régulière et le formatage de chaîne standard:

# 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'

L'appel re.sub () remplace toutes les séquences d'esperluette suivies d'une lettre par le motif% (..) s contenant le même motif.

La mise en forme% tire parti d'une fonctionnalité de mise en forme de chaîne qui peut prendre un dictionnaire pour spécifier la substitution, plutôt que les arguments positionnels les plus courants.

Une alternative peut le faire directement dans le re.sub, en utilisant un callback:

>>> import re
>>> def dictsub(m):
>>>    return dict[m.group()]
>>> str = re.sub(r'(&[a-zA-Z])', dictsub, str)

Cette fois, j'utilise une fermeture pour référencer le dictionnaire depuis l'intérieur de la fonction de rappel. Cette approche pourrait vous donner un peu plus de flexibilité. Par exemple, vous pouvez utiliser quelque chose comme dict.get(m.group(), '??') pour éviter de déclencher des exceptions si vous avez des chaînes avec des séquences de code non reconnues.

(Au fait, "dict" et "str" ​​sont des fonctions intégrées, et vous aurez des problèmes si vous utilisez beaucoup ces noms dans votre propre code. Juste au cas où vous ne le sauriez pas. une question comme celle-ci bien sûr.)

Edit: j'ai décidé de vérifier le code de test de Tor, et a conclu qu'il est loin d'être représentatif, et en fait buggy. La chaîne générée ne contient même pas de perluète (!). Le code révisé ci-dessous génère un dictionnaire représentatif et une chaîne, similaires aux exemples d'entrées du PO.

Je voulais également vérifier que la sortie de chaque algorithme était la même. Vous trouverez ci-dessous un programme de test révisé, avec uniquement le code de Tor, le mien et Claudiu, car les autres affichaient un éclatement. (Je pense qu'ils sont tous cassants à moins que le dictionnaire mappe fondamentalement all séquences possibles, que le code de test de Tor faisait.) Celui-ci génère correctement le générateur de nombres aléatoires de sorte que chaque exécution est la même. Enfin, j'ai ajouté une variante mineure à l'aide d'un générateur qui évite une surcharge des appels de fonctions, pour une amélioration mineure des performances.

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

J'ai oublié d'inclure les résultats des tests avant:

 Courant 10000 fois avec une longueur de chaîne de 1000 et 96 esperluettes 
 ('Tor fixe & variable dict', 2.9890000820159912) 
 ('Pierre fixe & variable dict', 2.6659998893737793) [.____. .] ('Peter fixed dict', 1.0920000076293945) 
 ('Peter générateur', 1.0460000038146973) 
 ('Claudio fixed dict', 1.562000036239624) 

En outre, des extraits des entrées et une sortie correcte:

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...'

En comparant avec ce que j'ai vu de la sortie du code de test de Tor:

mystr = 'VVVVVVVPPPPPPPPPPPPPPPXXXXXXXXYYYFFFFFFFFFFFFEEEEEEEEEEE...'
mydict = {'&p': '112', '&q': '113', '&r': '114', '&s': '115', ...}
output = # same as mystr since there were no ampersands inside
14
Peter Hansen

Si vous voulez vraiment creuser le sujet, jetez un coup d'œil à ceci: http://en.wikipedia.org/wiki/Aho-Corasick_algorithm

La solution évidente consiste à parcourir le dictionnaire et à remplacer chaque élément de la chaîne en prenant O(n*m) time, où n est la taille du dictionnaire et m la longueur de la chaîne.

Tandis que l'algorithme Aho-Corasick trouve toutes les entrées du dictionnaire dans O(n+m+f) où f est le nombre d'éléments trouvés.

8
WolfgangP

Si le nombre de clés dans la liste est grand et que le nombre d'occurrences dans la chaîne est faible (et généralement nul), vous pouvez effectuer une itération sur les occurrences des esperluettes de la chaîne et utiliser le dictionnaire associé au premier. caractère des sous-chaînes. Je ne code pas souvent en python, le style est peut-être un peu décalé, mais voici ce que je pense:

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

Bien sûr, il y a une question qui se passe quand il y a une esperluette qui provient de la chaîne elle-même, vous auriez besoin de l'échapper d'une manière ou d'une autre avant de passer à travers ce processus, puis de vous évader après ce processus. 

Bien entendu, comme il est d'usage pour les problèmes de performances, il est judicieux de synchroniser les différentes approches sur votre jeu de données typique (et dans le pire des cas) et de les comparer.

EDIT: placez-le dans une fonction séparée pour travailler avec un dictionnaire arbitraire:

def mysubst(somestr, somedict):
  subs = somestr.split("&")
  return subs[0] + "".join(map(lambda arg: somedict["&" + arg[0:1]] + arg[1:], subs[1:]))

EDIT2: élimine une concaténation inutile, semble toujours être un peu plus rapide que la précédente sur de nombreuses itérations.

def mysubst(somestr, somedict):
  subs = somestr.split("&")
  return subs[0].join(map(lambda arg: somedict["&" + arg[0:1]] + arg[1:], subs[1:]))
6
Andrew Y

Voici l'approche des extensions C pour python

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;
}

Codes Python que j'ai testés

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]

Résultats

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.

Il est supposé pouvoir fonctionner à O(n) , et N'a pris que 160 ms (avg) pour 11 Mo chaîne dans mon mobile Celeron 1,6 GHz PC

Il ignorera également les caractères inconnus tels quels, par exemple &Unknown retournera tel quel.

Faites-moi savoir Si vous rencontrez des problèmes de compilation, des bugs, etc ...

4
YOU

Cela semble que cela fait ce que vous voulez - plusieurs chaînes remplacent en même temps en utilisant RegExps. Voici le code pertinent:

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)
3
Claudiu

Une solution générale pour définir les règles de remplacement consiste à utiliser la substitution de regex en utilisant une fonction pour fournir la carte (voir 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 )

Ceci est particulièrement pratique pour les substitutions non triviales (par exemple, tout ce qui nécessite des opérations mathématiques pour créer le substitut).

3
charstar

Voici une version utilisant split/join

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)

Si des esperluettes contiennent des codes non valides, vous pouvez les utiliser pour les conserver.

myparts[1:]=[mydict.get(x[0],"&"+x[0])+x[1:] for x in myparts[1:]]

Peter Hansen a souligné que cela échouait lorsqu'il y avait double et commercial. Dans ce cas, utilisez cette version

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)
3
John La Rooy

Étant donné que quelqu'un a mentionné l'utilisation d'un analyseur syntaxique simple, j'ai pensé en préparer un à l'aide de pyparsing. En utilisant la méthode transformString de pyparsing, pyparsing analyse en interne la chaîne source et crée une liste du texte correspondant et du texte intermédiaire. Lorsque tout est terminé, transformString fait ensuite "'.join" de cette liste, il n'y a donc aucun problème de performance lors de la création de chaînes par incréments. (L'action d'analyse définie pour ANSIreplacer convertit les caractères & _ correspondants en séquence d'échappement souhaitée et remplace le texte correspondant par le résultat de l'action d'analyse. Seules les séquences correspondantes satisferont l'expression de l'analyseur. action d'analyse pour gérer les séquences non définies & _.) 

FollowedBy ('&') n'est pas strictement nécessaire, mais il raccourcit le processus d'analyse en vérifiant que l'analyseur est bien positionné sur une esperluette avant de procéder à la vérification plus coûteuse de toutes les options de marquage.

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)

Impressions:

'The \x1b[0;30mquick \x1b[0;31mbrown \x1b[0;32mfox \x1b[0;33mjumps over 
 the \x1b[0;34mlazy dog & &Zjumps back'

Cela ne gagnera certainement pas de concours de performance, mais si votre balisage commence à devenir plus compliqué, alors disposer d’une base d’analyse syntaxique facilitera son extension.

1
PaulMcG

Vous n'êtes pas sûr de la vitesse de cette solution, mais vous pouvez simplement parcourir votre dictionnaire et appeler à plusieurs reprises le système intégré. 

str.replace(old, new) 

Cela pourrait très bien fonctionner si la chaîne d'origine n'est pas trop longue, mais cela en souffrirait évidemment si la chaîne s'allongeait.

1
Michael Patterson

Le problème de ce remplacement en masse en Python est l’immuabilité des chaînes: chaque fois que vous remplacez un élément de la chaîne, une nouvelle chaîne entière est réallouée à plusieurs reprises à partir du segment de mémoire.

Donc, si vous voulez la solution la plus rapide, vous devez soit utiliser un conteneur mutable (par exemple, une liste), soit écrire cette machine en clair C (ou mieux en Pyrex ou Cython). Dans tous les cas, je suggérerais d'écrire un analyseur syntaxique simple basé sur une simple machine à états finis et de nourrir les symboles de votre chaîne un par un.

Des solutions suggérées basées sur des expressions rationnelles fonctionnant de la même manière, car les expressions rationnelles utilisant fsm en arrière-plan.

1
bialix
>>> 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)
0
ghostdog74

essaye ç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"])

0
fahad