为什么S == S [:: - 1]比循环更快?

时间:2015-06-02 16:18:09

标签: python string performance palindrome

为什么以pythonic方式检查字符串S是否是回文 - S == S[::-1] - 比以下实现更快?

i = 0
j = len(S) - 1
while i < j:
    if S[i] != S[j]:
        return False
    i += 1
    j -= 1
return True

3 个答案:

答案 0 :(得分:7)

因为Python代码只编译为字节码,然后解释。这比在C代码中创建一个新的反向字符串要慢很多,然后将字符串与另一个字符串进行比较,使用更多的C代码。

请注意,这两种算法基本上都是O(N)复杂度; Python代码最多执行1/2 N次迭代,而字符串反转版本最多可以进行2次N次迭代,但渐渐地说,它没有任何区别。

由于两种算法都是O(N)线性方法,因此重要的是它们的常数成本,每次迭代需要多长时间。 s == s[::-1]的固定成本大大

答案 1 :(得分:1)

在最坏的情况下,

S == S[::-1]会很快,因为在这种情况下,一切都在C中运行,与基于Python的循环相比,这将是闪电般快速的。您的版本的唯一优势是它不会等待创建反向字符串才能开始比较,因此在那些情况下 * 对于大字符串S == S[::-1]实际上会很慢

我们可以稍微提高一点S == S[::-1]如果不是比较整体,我们会把字符串的前半部分和后半部分分开,并将上半部分与后半部分的反转版本进行比较。

def fast_half(S):
    length = len(S)
    half = length/2
    first, last = half + (length % 2), half
    return S[:first] == S[last:][::-1]

def fast_simple(S):
    return S == S[::-1]

让我们比较一下:

>>> S = 'A' * 10**5
>>> %timeit fast_simple(S)
10000 loops, best of 3: 88.7 µs per loop
>>> %timeit fast_half(S)
10000 loops, best of 3: 49.1 µs per loop
>>> S = 'A' * 10**6
>>> %timeit fast_simple(S)
1000 loops, best of 3: 1.03 ms per loop
>>> %timeit fast_half(S)
1000 loops, best of 3: 601 µs per loop

我们可以通过上半部分memoryview略微快速fast_half,下半部分不可能,因为内存视图不支持扩展切片。此外,它们仅适用于str(Python 3中的bytes)和bytearrays

def fast_half_memoryview(S):
    length = len(S)
    half = length/2
    first, last = half + (length % 2), half
    return memoryview(S)[:first] == S[last:][::-1]

>>> S = 'A' * 10**5
>>> %timeit fast_half_memoryview(S)
10000 loops, best of 3: 46 µs per loop
>>> S = 'A' * 10**6
>>> %timeit fast_half_memoryview(S)
1000 loops, best of 3: 523 µs per loop
>>> S = 'A' * 10**7
>>> %timeit fast_half(S)
100 loops, best of 3: 12.8 ms per loop
>>> %timeit fast_half_memoryview(S)
100 loops, best of 3: 10.8 ms per loop
>>> %timeit fast_simple(S)
100 loops, best of 3: 17.3 ms per loop

对于小字符串S == S[::-1]是最好的:

>>> S = 'A' * 10
>>> %timeit fast_half(S)
1000000 loops, best of 3: 721 ns per loop
>>> %timeit fast_half_memoryview(S)
1000000 loops, best of 3: 1.07 µs per loop
>>> %timeit fast_simple(S)
1000000 loops, best of 3: 353 ns per loop
>>> %timeit func(S)  # OP's code
1000000 loops, best of 3: 1.34 µs per loop

* 当创建反向字符串或切片时可能会减慢我们的速度:  

>>> S = 'A' * 10**6 + 'a'
>>> %timeit fast_simple(S)
1000 loops, best of 3: 976 µs per loop
>>> %timeit fast_half(S)
1000 loops, best of 3: 566 µs per loop
>>> %timeit fast_half_memoryview(S)
1000 loops, best of 3: 523 µs per loop
>>> %timeit func(S)
1000000 loops, best of 3: 382 ns per loop

答案 2 :(得分:0)

以下不是一个很好的基准测试,但每次运行它时,你的功能都比s == s[::-1]略微。我还试图改变执行的顺序(首先运行你的函数),它仍然提供相同的结果!

import timeit

def is_pali1(s):
    s = str(s)
    return s == s[::-1]


def is_pali2(S):
    S = str(S)
    i = 0
    j = len(S) - 1
    while i < j:
        if S[i] != S[j]:
            return False
        i += 1
        j -= 1
    return True

res = timeit.repeat("for x in range(99999900,100000000): is_pali1(x)", "from __main__ import is_pali1", number=100000)
print "is_pali1", sum(res)/len(res)
res = timeit.repeat("for x in range(99999900,100000000): is_pali2(x)", "from __main__ import is_pali2", number=100000)
print "is_pali2", sum(res)/len(res)

<强>输出

is_pali1 4.37230348587
is_pali2 4.67844009399

请注意,我将数字修改为两个函数中的字符串:s = str(s),因此不应偏置结果。 在Python 2.7.6上测试。