在不同字符类型之间插入空格的Python脚本:为什么这个*这么*慢?

在不同字符类型之间插入空格的Python脚本:为什么这个*这么*慢?,python,optimization,nlp,Python,Optimization,Nlp,我正在处理一些混合了多种语言的文本,我已经对它们进行了一些处理,它们的形式是单个字符列表(称为“字母”)。我可以通过简单地测试每个字符是否有大小写(使用一个名为“test_lang”的小函数)来判断每个字符是哪种语言。然后我想在不同类型的字符之间插入一个空格,这样我就不会有任何混合字符类型的单词。同时,我想在单词和标点之间插入一个空格(我在一个名为“punc”的列表中定义了这个空格)。我写了一个脚本,它以一种非常直接的方式做这件事,这对我来说是有意义的(见下文),但显然是错误的方法,因为它非常慢

我正在处理一些混合了多种语言的文本,我已经对它们进行了一些处理,它们的形式是单个字符列表(称为“字母”)。我可以通过简单地测试每个字符是否有大小写(使用一个名为“test_lang”的小函数)来判断每个字符是哪种语言。然后我想在不同类型的字符之间插入一个空格,这样我就不会有任何混合字符类型的单词。同时,我想在单词和标点之间插入一个空格(我在一个名为“punc”的列表中定义了这个空格)。我写了一个脚本,它以一种非常直接的方式做这件事,这对我来说是有意义的(见下文),但显然是错误的方法,因为它非常慢

谁能告诉我做这件事最好的方法是什么

# 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
#在阿拉伯语/外国混音之间以及单词和punc之间添加空格
已清理=“”
i=0

每次执行字符串连接时,都会创建一个新字符串。字符串越长,每次连接所需的时间就越长


您最好声明一个足够大的列表来存储输出的字符,并在最后将它们连接起来。

这里有一些事情:

  • 您对字符串中的每个字母调用了两次
    test\u lang()
    ,这可能是速度慢的主要原因
  • 在Python中连接字符串不是很有效,应该使用列表或生成器,然后使用(很可能是,
    '.join()
以下是我将采用的方法,使用:

这会将字母分组为相同语言的连续字母,无论它们是否标点符号。join(g)
将每个组转换回一个字符串,然后将其转换回一个字符串。join()组合这些字符串,在每个字符串之间添加一个空格


此外,如DSM评论中所述,确保
punc
是一个集合。

我建议一个完全不同的解决方案,应该非常快速:

import re
cleaned = re.sub(r"(?<!\s)\b(?!\s)", " ", letters, flags=re.LOCALE)
重新导入

cleaned=re.sub(r)(?假设
test\u lang
不是瓶颈,我会尝试:

''.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:])
)

这是一个使用
yield
的解决方案。我想知道它是否比您的原始解决方案运行得更快

这避免了原始文件中的所有索引,它只是在输入中迭代,保留前面的单个字符

如果您的需求在将来发生变化,那么这应该很容易修改

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

保持OP原始代码的基本逻辑,我们通过不做所有[i]和[i+1]索引来加速它。我们使用一个prevnext引用来扫描字符串,在next后面保留prev一个字符:

# 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()的功能,因此无法进行注释:)

编辑:删除了不应该存在的“return”语句(测试残留!)



编辑:也可以通过不在同一个字符上调用test_lang()两次来加速(在一个循环中调用next,然后在下一个循环中调用prev)。缓存test_lang(下一个)结果。

字母的典型长度是多少?
?我想我在某处读到字符串的
+=
运算符不是很快。他们说
“”。join(列出所有元素)
更好。@NPE-如果我在整个语料库上运行此操作,
字母的长度不到500万个字符。所以当我说这很慢时,它真的很慢。如果
清理后的
是一个
bytearray
,有什么区别吗?你想对最后一个字母做什么?”声明一个足够大的列表。。。"不是你在Python中做的事情。你让列表动态增长。你的诊断是正确的,但是没有可用的建议来修复它。这个答案没有太大帮助。事实上,一些简单的测试表明,原始解决方案与
字母的大小成线性比例,而schlemiel的算法是二次比例的ally。python似乎在做一些聪明的事情。CPython优化了此常见情况下的string+=操作。它知道字符串只被引用一次,因此可以就地修改字符串,而不是创建新字符串。@递归,因为您可以在摊销O(1)中追加到列表中,这也没什么帮助。我不确定这是否能解决混合语言问题。是的,这一逻辑与OP想要的根本不一样。@AaronDufour:我也不确定;关于“拉丁语,或者阿拉伯语”的评论给我的印象是,这正是他想要分开的。我可能错了。@Tim-是的,你是对的,分开阿拉伯文和拉丁文字符是她想要做的。:-)所以我认为这个解决方案行不通。@larapsodia:你试过了吗?这正是它应该工作的情况。我发现这只比原来的解决方案快15%左右。毫无疑问,这是一个进步,但似乎我们应该能够做得更好;测试表明python做了一些聪明的事情来保持原始解决方案的线性,而不是您期望的二次型。也就是说,您缺少标点符号的大小写,因此这不是很令人满意。@AaronDufour-标点符号大小写已处理。@AaronDufour:自Python 2.5以来使用
+
的字符串连接;如果可能的话,字符串现在可以在适当的位置延伸。画家施莱米尔不再是个问题:)聪明!我做了一个编辑来捕捉
# 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