1

我正在尝试将数据结构中的一组元素替换为其他值。在 python 的情况下,在字符串中进行这种替换似乎比在列表中要快得多(如下面的基准测试所揭示的)。有人可以解释为什么。

注意:这些测试是使用 python 2.7 执行的。

def string_replace_test(s, chars):
    """Replaces a set of chars to 0"""
    new = s
    for c in chars:
        new = new.replace(c, '0')
    return new

def list_replace_test(s, chars):
    """Replaces a set of chars to 0"""
    for a in xrange(len(s)):
        if s[a] in chars:
            s[a] = '0'

if __name__ == '__main__':
    import timeit
    s = """
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec
        etfringilla purus. Pellentesque bibendum urna at neque consectetur
        at tincidunt nulla luctus. Pellentesque augue lacus, interdum id
        lectus vitae, laoreet suscipit arcu.
        """
    s2 = list(s)
    chars = ['a', 'e', 'i', 'o', 'u']
    print(timeit.timeit("string_replace_test(s, chars)", setup="from __main__ import string_replace_test, s, chars"))
    print(timeit.timeit("list_replace_test(s2, chars)", setup="from __main__ import list_replace_test, s2, chars"))

输出:

5.09572291374
49.3243050575

使用范围():

5.01253795624
53.2320859432
4

4 回答 4

5

由于没有list.replace()功能,您构建了自己的功能,但选择了一种缓慢的方法。

改用列表推导:

def list_replace_test(s, chars):
    """Replaces a set of chars to 0"""
    return [a if a not in chars else '0' for a in s]

这仍然会比字符串替换慢,因为您无法避免此处的 Python 循环。

使用一组chars帮助:

chars = set(chars)

但替换文本中单个字符的最快方法实际上是完全不同的技术。用于str.translate()

from string import maketrans

map = maketrans('aeiou', '0' * 5)
def str_translate(s, map):
    return s.translate(map)

随着这些变化,时间变成:

>>> timeit.timeit("list_replace_test(s2, chars)", setup="from __main__ import list_replace_test, s2, chars")
28.60542392730713
>>> timeit.timeit("string_replace_test(s, chars)", setup="from __main__ import string_replace_test, s, chars")
4.002871990203857
>>> timeit.timeit("str_translate(s, map)", setup="from __main__ import str_translate, s, map")
0.7250571250915527

也将循环str.replace()呼叫从水中吹出来。

于 2013-08-24T09:05:18.353 回答
5

不同之处主要是因为它str.replace是用 C 实现的方法,它可以更快地遍历字符串。它还可以使用更简单的比较(使用简单的 C 函数)而不是调用 python 方法。

你可以很容易地看到巨大的不同:

In [3]: s = """
   ...:      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec
   ...:      etfringilla purus. Pellentesque bibendum urna at neque consectetur
   ...:      at tincidunt nulla luctus. Pellentesque augue lacus, interdum id
   ...:      lectus vitae, laoreet suscipit arcu.
   ...:      """

In [4]: s2 = list(s)

In [5]: %%timeit
   ...: s.replace('a', '0')
   ...: 
1000000 loops, best of 3: 545 ns per loop

In [6]: %%timeit
   ...: for i, el in enumerate(s2):
   ...:     if el == 'a':
   ...:         s2[i] = '0'
   ...: 
100000 loops, best of 3: 17.9 us per loop
In [7]: 17.9 * 1000 / 545
Out[7]: 32.84403669724771

如您所见str.replace,运行速度比纯 python 循环快 33 倍。即使您想要替换许多元音时您的列表代码应该更快(特别是如果您使用集合而不是列表作为chars参数),要替换的字符数必须很大才能使代码足够高效。

例如:

In [14]: %%timeit
    ...: for i, el in enumerate(s2):
    ...:     if el in 'abcdefghijklmnopqrstuvwxyz':
    ...:         s2[i] = '0'
    ...: 
100000 loops, best of 3: 16.4 us per loop

请注意,时间与以前几乎相同,而:

In [17]: %%timeit S = s
    ...: for c in 'abcdefghijklmnopqrstuvwxyz':
    ...:     S = S.replace(c, '0')
    ...: 
100000 loops, best of 3: 5.63 us per loop

仍然更快,但时间增加了 10 倍。

实际上,从字符串中更改某些字符的最快方法是使用该translate方法,该方法允许replace通过一次调用执行多个 s:

In [1]: import string

In [2]: table = string.maketrans('aeiou', '00000')

In [3]: s = """
   ...:      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec
   ...:      etfringilla purus. Pellentesque bibendum urna at neque consectetur
   ...:      at tincidunt nulla luctus. Pellentesque augue lacus, interdum id
   ...:      lectus vitae, laoreet suscipit arcu.
   ...:      """

In [4]: %timeit s.translate(table)
1000000 loops, best of 3: 557 ns per loop

请注意,它与单个所需的时间相同,str.replace但它在一次通过中完成所有替换,就像您为列表所拥有的代码一样。

请注意,在 python3str.translate中将比在 python2 中慢得多,特别是如果您只翻译几个字符。这是因为它必须处理 unicode 字符,因此使用 adict来执行翻译而不是索引字符串。

于 2013-08-24T09:08:26.400 回答
2

这里的速度差异有几个原因。主要的是,在您的第一个示例中,您对一个函数进行了五次调用:

for c in ['a', 'e', 'i', 'o', 'u']:
    new = new.replace(c, '0')

但是,在第二种情况下,您将遍历 259 个字符长的字符串,并至少对s[a] in chars以下每个进行调用(最终成为调用):

if s[a] in chars:
    s[a] = '0'

已经在那里,我们有 51 倍的电话,而且电话并不便宜。检查字符是否应该被替换比你调用的替换函数快得多,但这是后一个函数慢的一个重要原因。

于 2013-08-24T09:20:11.113 回答
1

请注意,您的第二个不仅测试 string.replace。它完全是不同的算法,你可以通过替换来找到一些chars加速set(chars)

def list_replace_set_test(s, chars):
    """Replaces a set of chars to 0"""
    chars = set(chars)
    for a in xrange(len(s)):
        if s[a] in chars:
            s[a] = '0'
于 2013-08-24T09:06:21.890 回答