0

我正在处理一些混合语言的文本,我已经对其进行了一些处理,并且采用单个字符列表(称为“字母”)的形式。我可以通过简单地测试它是否有大小写来判断每个字符是哪种语言(使用一个名为“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
4

6 回答 6

4

这里发生了一些事情:

  • 你调用test_lang()字符串中的每个字母两次,这可能是它慢的主要原因。
  • 在 Python 中连接字符串效率不高,您应该使用列表或生成器,然后使用str.join()(最有可能, ''.join())。

这是我将采用的方法,使用itertools.groupby()

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

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

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

于 2013-01-25T20:20:39.110 回答
2

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

http://en.wikipedia.org/wiki/Schlemiel_the_Painter's_algorithm

您最好声明一个足够大的列表来存储输出的字符,然后在最后加入它们。

于 2013-01-25T20:18:40.343 回答
1

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

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

这会在每个单词边界处插入一个空格(将单词定义为“字母数字字符序列,包括当前语言环境中的重音字符”,这在大多数情况下都应该有效),除非它是空格旁边的单词边界。

这应该在拉丁字符和阿拉伯字符之间以及拉丁和标点符号之间分开。

于 2013-01-25T20:26:13.253 回答
0

假设test_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:])
)
于 2013-01-25T23:36:39.927 回答
0

这是一个使用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))
于 2013-01-25T23:53:54.867 回答
0

保留 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 万个字符的字符串进行测试表明,这大约是 OP 代码速度的两倍。正如其他人指出的那样,“字符串连接很慢”的抱怨已经过时了。使用 ''.join(...) 隐喻再次运行测试显示执行速度比使用字符串连接要慢一些。

不调用 test_lang() 函数,而是通过内联一些简单的代码,可以进一步加快速度。无法发表评论,因为我真的不知道 test_lang() 做了什么:)。

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

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

于 2013-01-26T02:18:53.480 回答