python中的快速字符串修改

时间:2009-10-02 09:06:18

标签: python string

这部分是一个理论问题:

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

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


"01234" becomes "0a1b2c3d4e" 

等等。 我怀疑循环中的天真串联会太昂贵(它是瓶颈,这应该是一直发生的。)

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

对于这类事情,任何人都有更好的想法吗?

(请注意,问题始终是多字节编码,并且必须为UTF-8解决),

哦,它的Python 2.5,所以这里没有闪亮的Python 3东西。

由于

7 个答案:

答案 0 :(得分:10)

@gnosis,谨防所有善意的回应者说你应该衡量时间:是的,你应该(因为程序员的直觉往往是关于绩效的基础),但是衡量一个如同到目前为止提供的所有timeit例子一样,错过了一个重要的考虑因素 - big-O

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

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

我还注意到,似乎没有人遵循您的规范中的一个关键和难点:字符串是Python 2.5多字节,UTF-8编码,str,并且插入必须在每个“完成”之后发生在每个字节之后,字符“,。每个人似乎都是“在str上循环”,它给出了每个字节,而不是每个字符,如你所清楚指定的那样。

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

所以我强烈建议您考虑这种“最佳方法”并相应地重构您的代码:在任何地方使用unicode,除非在必要时可以编码/解码的边界除外。对于“处理”部分,使用unicode对象比使用balky多字节编码字符串更快乐! - )

修改:忘记评论您提及的可能方法 - append。这确实是O(N)如果你只是追加到你正在构建的新数组的末尾(一些追加将使数组增长超出以前分配的容量,因此需要重新分配和复制数据,就像列表一样,中间指数的分配策略允许insert 摊销 O(1),因此N附加为O(N))。

但是,通过在O(N**2)中间重复array.array('u')次操作来构建数组(再次,就像列表一样),因为每个O(N)插入必须全部移位O(N)以下项目(假设先前存在的项目的数量和新插入项目的数量彼此成比例,就像您的具体要求的情况一样)。

所以,append,重复insert s( join s! - ),是第四种O(N)方法可以解决您的问题(除了我之前提到的三个问题:recStringIO和{{1}}) - 那些是值得您进行基准测试的问题澄清我感兴趣的长度范围,如上所述。

答案 1 :(得分:2)

尝试使用re module构建结果。它将在引擎盖下进行令人讨厌的连接,因此性能应该没问题。例如:

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

答案 2 :(得分:2)

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

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

答案 3 :(得分:1)

你测试过它有多慢,或者你需要多快,我认为这样的事情会很快

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

答案 4 :(得分:1)

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

答案 5 :(得分:0)

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

答案 6 :(得分:0)

string="™¡™©€"
unicode(string,"utf-8")
s2='-'*len(s1)
''.join(sum(map(list,zip(s1,s2)),[])).encode("utf-8")