python中字符串的快速修改

python中字符串的快速修改,python,string,Python,String,这部分是一个理论问题: 我有一个字符串(比如UTF-8),我需要修改它,使每个字符(不是字节)变成2个字符,例如: "Nissim" becomes "N-i-s-s-i-m-" "01234" becomes "0a1b2c3d4e" 等等。 我怀疑在循环中进行简单的连接代价太高(这是瓶颈,应该一直都会发生) 我要么使用一个数组(预先分配的),要么尝试制作自己的C模块来处理这个问题 有人对这类事情有更好的想法吗 (请注意,问题始终与多字节编码有关,对于UTF-8也必须解决) 哦,还有它

这部分是一个理论问题:

我有一个字符串(比如UTF-8),我需要修改它,使每个字符(不是字节)变成2个字符,例如:

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


"01234" becomes "0a1b2c3d4e" 
等等。 我怀疑在循环中进行简单的连接代价太高(这是瓶颈,应该一直都会发生)

我要么使用一个数组(预先分配的),要么尝试制作自己的C模块来处理这个问题

有人对这类事情有更好的想法吗

(请注意,问题始终与多字节编码有关,对于UTF-8也必须解决)

哦,还有它的Python2.5,所以这里没有闪亮的Python3


谢谢

你测试过它有多慢或者你需要多快吗,我想这样的东西足够快了

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

试着用这个函数来构建结果。它会在引擎盖下进行令人讨厌的连接,所以性能应该是可以的。例如:

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

这可能是一个python有效的解决方案:

s1="Nissim"
s2="------"
s3=''.join([''.join(list(x)) for x in zip(s1,s2)])
使用Reduce

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

这是我的时间安排。注意,它是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

@gnosis,当心所有善意的回应者说你应该衡量时间:是的,你应该(因为程序员的直觉通常是对性能的偏离),但是衡量单个案例,就像迄今为止提供的所有
timeit
示例一样,忽略了一个重要的考虑因素--大O

你的直觉是正确的:一般来说(在一些特殊情况下,最新的Python版本可以稍微优化一些东西,但它们不会延伸太远),通过
+=
循环在片段上构建字符串(或者
reduce
等等)必须是
O(N**2)
由于许多中间对象分配和不可避免的重复复制这些对象的内容;连接、正则表达式和上述答案中未提及的第三个选项(
write
method of
cStringIO.StringIO
实例)是
O(N)
解决方案,因此是唯一值得考虑的解决方案,除非您碰巧知道要操作的字符串长度有适当的上限

那么,如果有,您正在处理的字符串的长度上限是多少?如果你能给我们一个想法,基准测试可以在有代表性的感兴趣的长度范围内运行(例如,“通常少于100个字符,但有些时候可能是几千个字符”这将是一个很好的性能评估规范:瞧,它不需要非常精确,只是指示您的问题空间)

我还注意到,在您的规范中,似乎没有人遵循一个关键和难点:字符串是Python 2.5多字节、UTF-8编码、
str
s,并且插入只能发生在每个“完整字符”之后,而不是每个字节之后。每个人似乎都在“在str上循环”,它给出了每个字节,而不是您明确指定的每个字符

在多字节编码的byte
str
中,确实没有好的、快速的“循环字符”方法;最好的方法是
.decode('utf-8')
,给出一个unicode对象——处理unicode对象(循环可以正确地遍历字符!),然后
.encode
在最后将其重新编码。到目前为止,最好的方法通常是在整个代码的核心部分只使用unicode对象,notencoded
str
s;仅在I/O时对字节字符串进行编码和解码(如果必须,因为您需要与仅支持字节字符串而不支持正确Unicode的子系统通信)

<>所以我强烈建议你考虑这个“最佳方法”并相应地重构你的代码:无处不在的Unicode,除非它在必要时和必要时被编码/解码的边界。对于“处理”部分,使用unicode对象要比使用笨拙的多字节编码字符串快乐得多

编辑:忘了评论您提到的一种可能的方法--
array.array
。如果您只是在构建的新数组的末尾追加,那么这确实是O(N)(有些附件会使阵列的容量超过以前分配的容量,因此需要重新分配和复制数据,但是,就像列表一样,中指数式的过度分配策略允许将
附件
分摊到O(1),因此N个附件将分摊到O(N))

但是,通过重复的<代码>插入INS/代码>来构建一个数组(同样,就像一个列表),中间的操作是<代码> O(n** 2),因为每个O(n)插入都必须移动所有O(n)以下项(假设先前存在的项目数量和新插入的项目数量彼此成比例,就像您的具体需求一样)


因此,一个
array.array('u')
,在其上重复添加
s(not
insert
s!-),是可以解决您的问题的第四个O(N)方法(除了我已经提到的三个:
join
re
、和
cStringIO
)--正如我前面提到的,一旦你明确了感兴趣的长度范围,这些都是值得进行基准测试的。

你说的“太贵”是什么意思?你觉得这种天真的方法有多贵?从来没有对它进行过基准测试,但我怀疑内存会被再次分配、释放和重新分配,在输入的N个字符中分配、释放和重新分配N次。现在就对它进行分析。Python优化了字符串操作。不要假设某个东西太贵,我
string="™¡™©€"
unicode(string,"utf-8")
s2='-'*len(s1)
''.join(sum(map(list,zip(s1,s2)),[])).encode("utf-8")