быстрая модификация строки в python - PullRequest
4 голосов
/ 02 октября 2009

Это частично теоретический вопрос:

У меня есть строка (скажем, UTF-8), и мне нужно изменить ее так, чтобы каждый символ (не байт) становился 2 символами, например:

"Nissim" becomes "N-i-s-s-i-m-"


"01234" becomes "0a1b2c3d4e" 

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

Я бы либо использовал массив (предварительно выделенный), либо попытался бы создать собственный модуль C, чтобы справиться с этим.

У кого-нибудь есть лучшие идеи для такого рода вещей?

(обратите внимание, что проблема всегда связана с многобайтовыми кодировками, и должна решаться и для UTF-8),

Да, и его Python 2.5, поэтому здесь нет доступных блестящих штук для Python 3.

Спасибо

Ответы [ 7 ]

10 голосов
/ 02 октября 2009

@ gnosis, остерегайтесь всех благонамеренных респондентов, говорящих, что вы должны измерить время: да, вы должны (потому что инстинкты программистов часто не основаны на производительности), , но измеряют один случай как и во всех приведенных выше timeit примерах, пропущен важный момент: big-O .

Ваши инстинкты верны: в общем (с очень немногими особыми случаями, когда недавние выпуски Python могут немного оптимизировать вещи, но они не слишком сильно растягиваются), создавая строку с помощью цикла += над частями (или reduce и т. Д.) Должно быть O(N**2) из-за большого количества промежуточных распределений объектов и неизбежного повторного копирования содержимого этого объекта; объединение, регулярные выражения и третий вариант, который не был упомянут в вышеприведенных ответах (write метод cStringIO.StringIO экземпляров), являются решениями O(N) и, следовательно, единственными, которые стоит рассмотреть, если только вы не знаете точно, что строки, с которыми вы будете работать, имеют скромные верхние границы своей длины.

Так, каковы, если таковые имеются, верхние границы длины строк, которые вы обрабатываете? Если вы можете дать нам идею, тесты могут быть выполнены на репрезентативных диапазонах интересующих длин (например, скажем, «чаще всего менее 100 символов, но в некоторых% случаев может быть пара тысяч символов») было бы отличной спецификацией для этой оценки производительности: IOW, она не должна быть очень точной, просто указывающей на ваше проблемное пространство).

Я также замечаю, что никто не следит за одним критическим и трудным моментом в ваших спецификациях: строки представляют собой многобайтовые символы Python 2.5, кодируются в кодировке UTF-8, str с, и вставки должны выполняться только после каждого "завершенного символа" не после каждого байта . Кажется, что все "зацикливаются на строке", которые дают каждый байт, а не каждый символ , как вы четко указали.

На самом деле нет хорошего, быстрого способа "перебрать символы" в многобайтовом кодированном байте str; лучшее, что можно сделать, - это .decode('utf-8'), предоставляющий объект Unicode - обработать объект Unicode (где циклы do правильно перебирают символы!), затем .encode его обратно в конце. Безусловно, лучший подход в общем случае состоит в том, чтобы использовать исключительно юникодные объекты, , а не , закодированные str s, по всему сердцу вашего кода; кодировать и декодировать в / из байтовых строк только при вводе / выводе (если и когда это необходимо, потому что вам нужно взаимодействовать с подсистемами, которые поддерживают только байтовые строки и не используют надлежащий Unicode).

Поэтому я настоятельно рекомендую вам рассмотреть этот «наилучший подход» и соответствующим образом реструктурировать свой код: юникод везде, кроме тех границ, где он может быть закодирован / декодирован только в случае необходимости. Что касается «обработки», вы будете НАМНОГО счастливее с объектами Unicode, чем та, что тащили бы за блокированные многобайтовые строки! -)

Редактировать : забыл прокомментировать возможный подход, о котором вы упомянули - array.array. Это действительно O (N) , если , вы только добавляете к концу нового массива, который вы строите (некоторые добавления приведут к росту массива сверх ранее выделенной емкости и, следовательно, требуют перераспределение и копирование данных, , но , как и для списка, стратегия средне экспоненциального перераспределения позволяет append быть амортизированным O (1), и, следовательно, N добавляется к O ( N)).

Однако, чтобы построить массив (опять же, как список) с помощью повторяющихся insert операций в середине его, это O(N**2), потому что каждая из вставок O (N) должна сдвигать все O (N) следующие элементы (при условии, что количество ранее существующих элементов и количество вновь вставленных элементов пропорционально друг другу, что, как представляется, соответствует вашим конкретным требованиям).

Итак, array.array('u'), с повторяющимися append с ( не insert с! -), является четвертым O (N) подходом, который может решить вашу проблему (в дополнение к трем, которые я уже упомянул: join, re и cStringIO) - эти - те, которые стоит сравнить, когда вы уточните диапазоны длин, которые представляют интерес, как я упоминал выше .

2 голосов
/ 02 октября 2009

это может быть эффективное решение для Python:

s1="Nissim"
s2="------"
s3=''.join([''.join(list(x)) for x in zip(s1,s2)])
2 голосов
/ 02 октября 2009

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

 import re
 re.sub(r'(.)', r'\1-', u'Nissim')

 count = 1
 def repl(m):
     global count
     s = m.group(1) + unicode(count)
     count += 1
     return s
 re.sub(r'(.)', repl, u'Nissim')
1 голос
/ 02 октября 2009

вот мои времена. Обратите внимание, это py3.1

>>> s1
'Nissim'
>>> s2 = '-' * len(s1)
>>> timeit.timeit("''.join(i+j for i, j in zip(s1, s2))", "from __main__ import s1, s2")
3.5249209707199043
>>> timeit.timeit("''.join(sum(map(list,zip(s1,s2)),[]))", "from __main__ import s1, s2")
5.903614027402
>>> timeit.timeit("''.join([''.join(list(x)) for x in zip(s1,s2)])", "from __main__ import s1, s2")
6.04072124013328
>>> timeit.timeit("''.join(i+'-' for i in s1)", "from __main__ import s1, s2")
2.484378367653335
>>> timeit.timeit("reduce(lambda x, y : x+y+'-', s1, '')", "from __main__ import s1; from functools import reduce")
2.290644129319844
1 голос
/ 02 октября 2009

проверили ли вы, насколько медленно или быстро вам нужно, я думаю, что-то вроде этого будет достаточно быстрым

s = u"\u0960\u0961"
ss = ''.join(sum(map(list,zip(s,"anurag")),[]))

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

Редактировать: Это также самый быстрый

import timeit

s1="Nissim"
s2="------"

timeit.f1=lambda s1,s2:''.join(sum(map(list,zip(s1,s2)),[]))
timeit.f2=lambda s1,s2:''.join([''.join(list(x)) for x in zip(s1,s2)])
timeit.f3=lambda s1,s2:''.join(i+j for i, j in zip(s1, s2))

N=100000

print "anurag",timeit.Timer("timeit.f1('Nissim', '------')","import timeit").timeit(N)
print "dweeves",timeit.Timer("timeit.f2('Nissim', '------')","import timeit").timeit(N)
print "SilentGhost",timeit.Timer("timeit.f3('Nissim', '------')","import timeit").timeit(N)

вывод

anurag 1.95547590546
dweeves 2.36131184271
SilentGhost 3.10855625505
0 голосов
/ 02 октября 2009
string="™¡™©€"
unicode(string,"utf-8")
s2='-'*len(s1)
''.join(sum(map(list,zip(s1,s2)),[])).encode("utf-8")
0 голосов
/ 02 октября 2009

Используйте Уменьшить.

>>> str = "Nissim"
>>> reduce(lambda x, y : x+y+'-', str, '')
'N-i-s-s-i-m-'

То же самое и с числами, если вы знаете, какой символ сопоставлен с каким. [dict может быть удобно]

>>> mapper = dict([(repr(i), chr(i+ord('a'))) for i in range(9)])
>>> str1 = '0123'
>>> reduce(lambda x, y : x+y+mapper[y], str1, '')
'0a1b2c3d'
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...