python中字符串的快速修改
这部分是一个理论问题: 我有一个字符串(比如UTF-8),我需要修改它,使每个字符(不是字节)变成2个字符,例如:python中字符串的快速修改,python,string,Python,String,这部分是一个理论问题: 我有一个字符串(比如UTF-8),我需要修改它,使每个字符(不是字节)变成2个字符,例如: "Nissim" becomes "N-i-s-s-i-m-" "01234" becomes "0a1b2c3d4e" 等等。 我怀疑在循环中进行简单的连接代价太高(这是瓶颈,应该一直都会发生) 我要么使用一个数组(预先分配的),要么尝试制作自己的C模块来处理这个问题 有人对这类事情有更好的想法吗 (请注意,问题始终与多字节编码有关,对于UTF-8也必须解决) 哦,还有它
"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 ofcStringIO.StringIO
实例)是O(N)
解决方案,因此是唯一值得考虑的解决方案,除非您碰巧知道要操作的字符串长度有适当的上限
那么,如果有,您正在处理的字符串的长度上限是多少?如果你能给我们一个想法,基准测试可以在有代表性的感兴趣的长度范围内运行(例如,“通常少于100个字符,但有些时候可能是几千个字符”这将是一个很好的性能评估规范:瞧,它不需要非常精确,只是指示您的问题空间)
我还注意到,在您的规范中,似乎没有人遵循一个关键和难点:字符串是Python 2.5多字节、UTF-8编码、str
s,并且插入只能发生在每个“完整字符”之后,而不是每个字节之后。每个人似乎都在“在str上循环”,它给出了每个字节,而不是您明确指定的每个字符
在多字节编码的bytestr
中,确实没有好的、快速的“循环字符”方法;最好的方法是.decode('utf-8')
,给出一个unicode对象——处理unicode对象(循环可以正确地遍历字符!),然后.encode
在最后将其重新编码。到目前为止,最好的方法通常是在整个代码的核心部分只使用unicode对象,notencodedstr
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(notinsert
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")