为啥 str.replace 这么快?

Posted

技术标签:

【中文标题】为啥 str.replace 这么快?【英文标题】:Why is str.replace so fast?为什么 str.replace 这么快? 【发布时间】:2021-12-19 20:18:29 【问题描述】:

我目前正在学习和练习字符串算法。具体来说,我正在尝试用一些修改替换基于KMP 的字符串中的模式,这具有 O(N) 复杂度(我的实现如下)。

def replace_string(s, p, c):
    """
    Replace pattern p in string s with c
    :param s: initial string
    :param p: pattern to replace
    :param c: replacing string
    """
    pref = [0] * len(p)

    s_p = p + '#' + s
    p_prev = 0
    shift = 0

    for i in range(1, len(s_p)):
        k = p_prev

        while k > 0 and s_p[i] != s_p[k]:
            k = pref[k - 1]

        if s_p[i] == s_p[k]:
            k += 1

        if i < len(p):
            pref[i] = k

        p_prev = k

        if k == len(p):
            s = s[:i - 2 * len(p) + shift] + c + s[i - len(p) + shift:]
            shift += len(c) - k

    return s

然后,我用python内置的str.replace函数写了同样的程序:

def replace_string_python(s, p, c):
    return s.replace(p, c)

比较各种字符串的性能,我只附上一个例子,长度为 1e5 的字符串:

import time


if __name__ == '__main__':
    initial_string = "a" * 100000
    pattern = "a"
    replace = "ab"

    start = time.time()
    res = replace_string(initial_string, pattern, replace)

    print(time.time() - start)

输出(我的实现):

total time: 1.1617710590362549

输出(python 内置):

total time: 0.0015637874603271484

如您所见,通过 python str.replace 实现比 KMP 领先光年。所以我的问题是为什么? python C代码使用什么算法?

【问题讨论】:

我希望它仍然是 O(n),但它会更快,因为它是在 C 中实现的,而不必通过 Python 解释器。尝试将每个字符串延长 10 倍,看看这两个时间是否都增加了 ~10 倍。 举个例子,你在你的实现中构造了一堆字符串。您的s = s[...:] + c + s[:...] 将数据分配并复制到多达四个新的字符串对象中。 Python 对象比相应的 C 数据结构占用更多空间。而且,如果您将复制字符串内容所需的微循环计算为非原子的,那么从技术上讲,它不再是 O(N)(尽管您很难看到差异,因为这些循环比那里发生的其他事情)。 这里是C code header 的链接,用于.replace 方法。 @S3DEV,不幸的是,我对 C 的了解不够,无法理解该标头中发生了什么。基于之前的 cmets,我假设 str.replace 函数具有相同的 O(N) 复杂度,但它的编写效率更高。 ... 因为在您的代码中,每个+、每个:、每个[]=== 都是对专门的、非常快速的函数的调用用 C 语言编写。但是文书工作(对象创建、对象销毁、内存管理......)加起来,突然之间,一堆快速函数最终运行得很慢。一个建筑大师可以做捷径;但如果你有一个完整的组织,捷径最终会害死人(或者让你的代码窒息,可能是这样)。 【参考方案1】:

虽然算法可能是 O(N),但您的实现似乎不是线性的,至少对于模式的多次重复而言不是,因为

 s = s[:i - 2 * len(p) + shift] + c + s[i - len(p) + shift:]

本身就是 O(N)。因此,如果您的模式在一个字符串中出现 N 次,那么您的实现实际上是 O(N^2)。

请参阅以下时间以了解您的算法的缩放时间,这可以确认二次形状

LENGTH  TIME
------------
100000    1s
200000    8s
300000   31s
400000   76s
500000  134s

【讨论】:

有什么方法可以更快地进行替换吗? 你不能在 python 的次线性时间内构造一个长度为 N 的字符串,因为字符串是不可变的。您需要对可变数据类型进行操作,例如列表。然后您可以在不重新分配内存的情况下进行编辑(因此复杂性与更改的字符成正比)。对于恒定时间连接,您可能需要一个链表类型(不是在 python 中内置的)。总体而言,python 并不是学习“核心”算法的最佳选择,因为它缺乏非常有效的数据类型,C++ 是 def。效率和低级算法的更好场所。 Nitpick:“链表类型(不是在 python 中内置的)”——这不是真的:collections.deque 实现了它。虽然它没有那么有用,因为您没有能力直接引用列表的中间,以便有效地拼接。 你能在 O(1) @Amadan 中连接两个双端队列吗?这是我们在这里需要的链表的关键属性我认为 deque 只是在内部实现为链表(实际上是数组的链表或类似的东西),但没有充分发挥其功能? 同意@Amadan。为了替换链表中的节点(在 O(1) 中完成),必须先找到它,这需要 O(N),还是我错过了什么?

以上是关于为啥 str.replace 这么快?的主要内容,如果未能解决你的问题,请参考以下文章

python,如图选项replace的使用为啥是错的?谢谢

关于php strtr 和 str_replace 效率的问题

PHP str_replace()字符串匹配

textarea 按回车为啥不换行

PHP 过滤特殊符号

在js 中 replace 怎么不能替换中文?