Python script to insert space between different character types: Why is this *so* slow?
我正在处理一些混合语言的文本,我已经对这些文本进行了一些处理,并且这些文本的形式是单个字符列表(称为"字母")。我可以通过简单地测试每个字符是否有大小写(使用一个称为"test_lang"的小函数)来判断它是哪种语言。然后我想在不同类型的字符之间插入一个空格,所以我没有任何混合字符类型的单词。同时,我想在单词和标点之间插入一个空格(我在一个名为"punc"的列表中定义了这个空格)。我写了一个脚本,它以非常直截了当的方式完成了这项工作,这对我来说是有意义的(见下文),但显然是错误的方式,因为它的速度非常慢。
有人能告诉我做这个更好的方法是什么吗?
1 2 3 4 5 6 7 8 9 10 11 12 | # Add a space between Arabic/foreign mixes, and between words and punc cleaned ="" i = 0 while i <= len(letters)-2: #range excludes last letter to avoid Out of Range error for i+1 cleaned += letters[i] # words that have case are Latin; otherwise Arabic if test_lang(letters[i]) != test_lang(letters[i+1]): cleaned +="" if letters[i] in punc or letters[i+1] in punc: cleaned +="" i += 1 cleaned += letters[len(letters)-1] # add in last letter |
这里发生了一些事情:
- 对字符串中的每个字母调用
test_lang() 两次,这可能是速度慢的主要原因。 - 在python中连接字符串不是很有效,您应该使用一个列表或生成器,然后使用
str.join() (很可能是''.join() )。
下面是我将采用的方法,使用
1 2 3 4 5 | from itertools import groupby def keyfunc(letter): return (test_lang(letter), letter in punc) cleaned = ' '.join(''.join(g) for k, g in groupby(letters, keyfunc)) |
这将把字母组合成相同语言的连续字母,不管它们是否标点符号,然后
此外,正如DSM的评论所指出的,确保
每次执行字符串串联时,都会创建一个新的字符串。字符串得到的时间越长,每次连接所花费的时间就越长。
http://en.wikipedia.org/wiki/schlemiel-the-painter's-u算法
您最好声明一个足够大的列表来存储输出的字符,并在末尾加入它们。
我建议一个完全不同的解决方案,应该非常快:
1 2 | import re cleaned = re.sub(r"(?<!\s)\b(?!\s)","", letters, flags=re.LOCALE) |
这会在每个单词边界处插入一个空格(将单词定义为"字母数字字符序列,包括当前区域设置中的重音字符",这在大多数情况下都适用),除非它是靠近空白的单词边界。
这应该分为拉丁和阿拉伯字符以及拉丁和标点符号。
保持操作的原始代码的基本逻辑,我们不做所有的[i]和[i+1]索引来加速它。我们使用prev和next引用扫描字符串,保持prev在next后面一个字符:
1 2 3 4 5 6 7 8 9 10 11 | # Add a space between Arabic/foreign mixes, and between words and punc cleaned = '' prev = letters[0] for next in letters[1:]: cleaned += prev if test_lang(prev) != test_lang(next): cleaned += ' ' if prev in punc or next in punc: cleaned += ' ' prev = next cleaned += next |
对1000万个字符的字符串进行测试表明,这大约是操作代码速度的两倍。正如其他人指出的那样,"字符串串联很慢"的抱怨已经过时了。使用""隐喻再次运行测试。与使用字符串串联相比,join(…)隐喻的执行速度稍慢。
进一步的加速可能不是通过调用test_lang()函数,而是通过嵌入一些简单的代码来实现。无法评论,因为我不知道test_lang()的作用是什么。
编辑:删除了不应该存在的"返回"语句(测试剩余部分!).
编辑:也可以通过不在同一个字符上两次调用test_lang()来加快速度(在一个循环中的下一个循环中,然后在下面的循环中调用prev)。缓存测试语言(下一个)结果。
这里有一个使用
这样可以避免原始文件中的所有索引。它只是迭代输入,保留前一个字符。
如果您的需求在将来发生变化,这应该很容易修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | ch_sep = ' ' def _sep_chars_by_lang(s_input): itr = iter(s_input) ch_prev = next(itr) yield ch_prev while True: ch = next(itr) if test_lang(ch_prev) != test_lang(ch) or ch_prev in punc: yield ch_sep yield ch ch_prev = ch def sep_chars_by_lang(s_input): return ''.join(_sep_chars_by_lang(s_input)) |
假设
1 2 3 4 5 6 | ''.join( x + ' ' if x in punc or y in punc or test_lang(x) != test_lang(y) else x for x, y in zip(letters[:-1], letters[1:]) ) |