Массовую строку заменить в питоне? - PullRequest
42 голосов
/ 17 декабря 2009

Скажем, у меня есть строка, которая выглядит следующим образом:

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 пар).

Любые предложения приветствуются, спасибо.

Edit:

Как отмечалось в комментариях к этому вопросу, мой словарь определен до времени выполнения и никогда не изменится в течение жизненного цикла приложений. Это список управляющих последовательностей ANSI, в котором будет около 40 элементов. Моя средняя длина строки для сравнения будет около 500 символов, но будут и те, которые до 5000 символов (хотя, они будут редкими). Я также использую Python 2.6 в настоящее время.

Редактировать # 2 Я принял ответ Tor Valamos как правильный, поскольку он не только дал правильное решение (хотя это не было best решение), но принял во внимание все остальные и проделал огромную работу для сравнения все они. Этот ответ - один из лучших, самых полезных ответов, которые я когда-либо встречал на StackOverflow. Слава тебе.

Ответы [ 13 ]

30 голосов
/ 17 декабря 2009
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

Оба решения Клавдия и Эндрю продолжали идти в 0, поэтому мне пришлось увеличить его до 10 000 прогонов.

Я запустил его в Python 3 (из-за юникода) с заменами символов с 39 на 1024 (38 - амперсанд, поэтому я не хотел его включать). Длина строки до 10.000, включая около 980 замен с переменными случайными вставками длиной 0-20. Значения Unicode от 39 до 1024 приводят к появлению символов длиной как 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, где ключи не содержат '&'. Код Эндрю также использует этот альтернативный dict, но это не имело большого значения, возможно, только 0,01x ускорив.)

14 голосов
/ 17 декабря 2009

Попробуйте, используя подстановку регулярного выражения и стандартное форматирование строки:

# 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 () заменяет все последовательности амперсанда, за которыми следует одна буква, с шаблоном% (..) 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, мой и код Клаудиу, поскольку остальные взламывали входные данные примера. (Я думаю, что все они хрупкие, если только словарь не отображает в основном все возможные последовательности амперсандов, что делал тестовый код 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

Я забыл включить результаты тестов раньше:

    Running 10000 times with string length 1000 and 96 ampersands
    ('Tor fixed & variable dict     ', 2.9890000820159912)
    ('Peter fixed & variable dict   ', 2.6659998893737793)
    ('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
8 голосов
/ 17 декабря 2009

Если вы действительно хотите покопаться в теме, взгляните на это: http://en.wikipedia.org/wiki/Aho-Corasick_algorithm

Очевидное решение: выполнить итерацию по словарю и заменить каждый элемент в строке на O(n*m) время, где n - размер словаря, m - длина строки.

Принимая во внимание, что алгоритм Aho-Corasick находит все записи словаря в O(n+m+f), где f - количество найденных элементов.

6 голосов
/ 17 декабря 2009

Если количество ключей в списке велико, а число вхождений в строке мало (и в основном равно нулю), то вы можете перебирать вхождения вхождения амперсандов в строке и использовать словарь с ключом по первому символу подстрок. Я не часто пишу код на 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:]))
4 голосов
/ 17 декабря 2009

Вот подход C Extensions для 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;
}

Коды 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) , и Потребовалось только 160 мс (в среднем) для строки 11 МБ в ПК My Mobile Celeron 1.6 ГГц

Также будут пропущены неизвестные символы, как есть, например, &Unknown вернется как есть

Дайте мне знать, если у вас возникли проблемы с компиляцией, ошибками и т. Д. *

3 голосов
/ 17 декабря 2009

Вот версия с использованием 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)

Если есть амперсанды с недействительными кодами, вы можете использовать их для их сохранения

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

Питер Хансен указал, что это невозможно, когда есть двойной амперсанд. В этом случае используйте эту версию

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 голосов
/ 17 декабря 2009

Общим решением для определения правил замещения является использование подстановки регулярных выражений с использованием функции для предоставления карты (см. 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 )

Это особенно хорошо для нетривиальных подстановок (например, всего, что требует математических операций для создания заменителя).

3 голосов
/ 17 декабря 2009

Похоже, что делает то, что вам нужно - несколько строк заменяются одновременно с помощью 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)
1 голос
/ 17 декабря 2009

Так как кто-то упомянул об использовании простого парсера, я подумал, что смогу его создать с помощью pyparsing. Используя метод transformString в pyparsing, pyparsing внутренне сканирует исходную строку и формирует список соответствующего текста и промежуточного текста. Когда все сделано, transformString затем '' .join в этом списке, так что нет проблем с производительностью при построении строк с шагом. (Действие parse, определенное для ANSIreplacer, выполняет преобразование сопоставленных символов & _ в требуемую escape-последовательность и заменяет сопоставленный текст выводом действия parse. Поскольку только соответствующие последовательности будут удовлетворять выражению синтаксического анализатора, нет необходимости в Действие синтаксического анализа для обработки неопределенных & _ последовательностей.)

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'

Это, безусловно, не выиграет конкурсы производительности, но если ваша разметка станет более сложной, то наличие основы синтаксического анализатора облегчит расширение.

1 голос
/ 17 декабря 2009

Проблема с выполнением этой массовой замены в Python заключается в неизменности строк: каждый раз, когда вы заменяете один элемент в строке, вся новая строка снова и снова перераспределяется из кучи.

Так что, если вы хотите самое быстрое решение, вам нужно либо использовать изменяемый контейнер (например, список), либо написать этот механизм на простом C (или лучше на Pyrex или Cython). В любом случае я бы предложил написать простой парсер на основе простого конечного автомата и поочередно кормить символы вашей строки.

Предлагаемые решения на основе регулярных выражений, работающих аналогичным образом, поскольку регулярные выражения работают с использованием fsm за сценой.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...