在 python 中获取子字符串是 O(n) 操作吗? [复制]
Posted
技术标签:
【中文标题】在 python 中获取子字符串是 O(n) 操作吗? [复制]【英文标题】:Is taking a substring in python an O(n) operation? [duplicate] 【发布时间】:2021-09-25 01:41:38 【问题描述】:在 C++ 中,如果我要从字符串中删除第一个字符,它看起来像:
string s = "myreallylongstring";
s = s.substr(1);
这将是 O(1)。 [如果我错了,请纠正我]
然而,在 Python 的“不可变字符串”世界中,这段代码是否在 O(n) 中运行?
s = "myreallylongstring"
s = s[1:]
如果我改用字符列表会更快吗?
【问题讨论】:
substr
在 C++ 中不是 O(1)。构造了一个新字符串。
substr
是 O(n)
。即使您知道在这种情况下n
恰好是1
,它仍然是O(n)
复杂性。 Big-O 表示法将复杂性增长描述为n
趋于无穷大。 O(1)
的大 O 复杂度严格来说意味着复杂度与给定变量无关。
std::basic_string::substr()
的复杂性与被提取的子字符串的长度呈线性关系。提取除第一个字符之外的所有字符将意味着复杂度 O(length),其中 length
是字符串的原始长度。 std::basic_string::operator=()
在分配的字符串长度上也是线性的。净效应:s=s.substr(1)
为 O(length)。
@FrançoisAndrieux s.substr(1)
中的1
是要提取的第一个字符的索引,而不是要提取的字符串的长度。
注意:这里的标题问题是 Time complexity of string slice 的副本(我显然回答了,但在过去 3 个正常年份 + 1.5 个 COVID 年份忘记了,后者算作 15 个正常年份当谈到记住以前的时间时)。额外的问题(关于 C++ substr
在 big-O 上更快的 OP 是否正确,Python list
会更有效)不是。我不会使用我的 C++ 和 Python dupehammer,但如果其他人认为这是骗子,我不会反对。
【参考方案1】:
在 Python 中切片任何内置类型(memoryview
除外)在一般情况下是 O(n)
(主要例外是不可变实例的完整切片,它通常返回原始实例而不复制它,因为它不是需要)。
list
的字符无济于事;从list
的开头删除一个字符是O(n)
(它必须将其上方的所有内容复制到一个插槽中)。可以想象,collections.deque
可以改进 big-O(他们可以从任一端弹出 O(1)
),但它也会比字符串消耗更多的内存和更多的碎片内存。
对于除了最大的字符串之外的所有字符串,即使有这些 O(n)
效率低下,您通常也可以,所以除非您实际分析并发现它是导致问题的原因,否则我会坚持切片并让它是。
也就是说,你对 C++ 的看法是错误的; s = s.substr(1)
与 Python 的 s = s[1:]
没有什么不同。它们最终都复制了整个字符串并保存了第一个字符,C++ move 分配回原始字符串,而 Python 用新对象替换了与旧对象的原始名称绑定(功能上非常相似的操作)。 s.substr(1)
甚至没有使用 std::string
的可变性特性; s.erase(0, 1)
实际上会就地擦除,改变原始字符串,但它仍然是O(n)
,因为所有其他字符都必须被复制到之前被删除的空间中字符(我所知道的没有std::string
实现允许存储从字符串开头的偏移量以查找真实数据,指向数据的指针始终指向第一个分配的字节)。
【讨论】:
还有另一种在 O(1) 中切片的内置类型,ShadowRanger。 @don'ttalkjustcode:哈,你说得对。我自己使用该功能(range(10)[::-1]
比 range(9, -1, -1)
更容易定义),但它是一种用途有限且灵活性很小的类型(如果您适当地使用 enumerate
/zip
/itertools
,几乎不需要) 我不怎么想。 :-)
是的,我以前也使用过[::-1]
,但可能像使用memoryview
一样经常对它们使用其他切片,即从不(除了测试它是否确实有效)。【参考方案2】:
这个答案解决了问题的 C++ 部分
s = s.substr(1);
创建一个新的临时字符串并将其复制到s
。操作是O(n)
。
更好的方法是:
s.erase(s.begin());
这仍然是O(n)
,但要避免创建新对象和复制。
更好的方法是使用std::string_view
:
auto sv = std::string_views.begin() + 1, s.end().
这是O(1)
,因为它不创建或更改任何字符串,它只是在现有字符串中创建一个视图。
【讨论】:
这不是对 OP 关于 Python 问题的回答,只是纠正他们对 C++ 的误解。另外,std::string
有 remove
方法吗?我只在the docs 中看到erase
。 erase
可以与 s.begin()
一起使用,也可以与 0, 1
一起使用(用于从索引 0 中删除 1 个字符)。
@ShadowRanger 是的,我确实只谈论 C++ 部分。是的,remove
打错了,是erase
。以上是关于在 python 中获取子字符串是 O(n) 操作吗? [复制]的主要内容,如果未能解决你的问题,请参考以下文章