在python中插入字符串的有效方法?

在python中插入字符串的有效方法?,python,Python,我目前正在编写一个解析器,用于检测一组源文件中的某些标识符。标识符随后将通过添加前缀进行重命名,以使其唯一。此重命名过程只能在处理完所有文件后进行,因为其中一些文件是链接的(例如,类和类的实例化需要获得相同的类标识符) 这个过程中的第一个步骤之一是临时剪切所有不必要的代码(字符串和括号内容+行/块注释),以使解析更容易、更省时。这些片段被切割并存储在一个deque中,名为tuples,具有以下结构:(index,value)。在解析器和重命名器完成它们的工作后,这些片段被粘贴回原始位置(由于文件

我目前正在编写一个解析器,用于检测一组源文件中的某些标识符。标识符随后将通过添加前缀进行重命名,以使其唯一。此重命名过程只能在处理完所有文件后进行,因为其中一些文件是链接的(例如,类和类的实例化需要获得相同的类标识符)

这个过程中的第一个步骤之一是临时剪切所有不必要的代码(字符串和括号内容+行/块注释),以使解析更容易、更省时。这些片段被切割并存储在一个deque中,名为tuples,具有以下结构:(index,value)。在解析器和重命名器完成它们的工作后,这些片段被粘贴回原始位置(由于文件更改而产生偏移)

前面的代码处理速度很快,但当我尝试通过将所有修剪的片段插入文件内容来重建文件时,出现了一个问题:

while self.trimmedCode:
    key, value = self.trimmedCode.pop()
    parsedContent = ''.join((parsedContent[:key],value,parsedContent[key:]))
某些文件包含大量字符串/注释,使重建过程非常缓慢(150000次插入需要6分钟)。我的问题?如何使索引处的插入更有效

由于字符串是不可变的,所以我尝试在执行所有插入之前将字符串转换为字符列表,以获得性能增益。这将while循环的速度提高约10%。但是,后续的联接操作会使获得的优势无效:

charList = list(parsedContent)

while self.trimmedCode:
    key, value = self.trimmedCode.pop()
    charList[key:key] = value

parsedContent = ''.join(charList)
我的问题:有没有更有效的方法来完成这项任务(使用Python2.7)

编辑:档案统计 相关档案统计信息:
信息:buildRenamedCopy重建文件并包含while循环,insertString在其中执行联接操作。此测试在较小文件的集合(+-600个文件)上运行


通过使用
+
而不是
'',您可以获得一些速度。join()
连接字符串:

while self.trimmedCode:
    key, value = self.trimmedCode.pop()
    parsedContent = parsedContent[:key] + value + parsedContent[key:]

通过在末尾使用单个字符串累加器进行连接,可以获得算法增益

大致如下:

lastkey = 0
accumulator = []
while self.trimmedCode:
    key, value = self.trimmedCode.pop()
    accumulator.extend((parsedContent[lastkey:key], value))
    lastkey = key
accumulator.append(parsedContent[lastkey:])
parsedContent = ''.join(accumulator)
它可能比你现在做的要快得多。如Blckknght所建议的,对于额外的点数,使用发电机而不是蓄能器


但是,如果速度不够快,您应该花时间查看,或者尝试一些现有的数据结构,对于这种情况可能更有效。我想尝试一下。

您的代码速度慢的原因是插入列表是
O(N)
(其中
N
是列表的长度)。这是因为插入点之后列表中的所有值都需要移动,以便为插入的新值腾出空间

您可以通过使用新字符串(插入的值加上前一个字符)替换给定位置的现有值,而不是执行增加列表的切片赋值来修复此问题。这仅在每个
都比之前的所有键都小(也就是说,如果您按降序迭代键)的情况下才有效

下面是一个经过最小修改的代码版本,它应该提高渐进性能(从
O(N^2)
O(N)
):

注意,您可能还可以将
while
pop
替换为更简单、更清晰的
for
循环:

for key, value in reversed(trimmedCode):
如果使用生成器函数生成要连接到更大块中的字符串序列,而不是将原始字符串拆分为单个字符,则可能会获得更好的性能。这不是一个渐进的性能变化,但可能会带来较大的常数因子改进。这里有一个尝试:

def insert_gen(orig_string, insertions):
    prev_key = 0
    for key, value in insertions:
        yield orig_string[prev_key:key] # yield text from the previous insert to this one
        yield value                     # yield the "inserted" text
        prev_key = key
    yield orig_string[prev_key:]        # yield trailing text (after last insert)
您可以这样使用它:

parsedContent = "".join(insert_gen(parsedContent, self.trimmedCode))

让你的代码清楚地识别瓶颈?作为补充,如果你真的有巨大的字符串操作,也许实现比字符串更可取。不知道是否有一些Python支持可用。问题经过编辑以包含探查器统计信息。为了确保我正确理解问题,是否按重要顺序弹出
trimmedCode
中的项目?也就是说,
索引是否考虑了插入以前的值所引起的移位?我假设是这样(或者您现有的代码可能不会给出正确的输出)。不幸的是,这使得修改算法以避免
O(N)
列表插入(以及
O(N^2)
总体运行时间)变得相当困难。出于好奇,探查器是否显示了
join
在哪里花费时间(内存分配?
memcpy
?…)。剩下的60秒(110秒用于
insertString
50秒用于
join
)?这并不能显著提高速度。+操作将insertString的累积时间从110秒“减少”到109秒。哦,该死,我没有注意到那里有一个二次性能错误。接得好。哇,我应该再看一眼“黑名单”的替代品。这将显著提高性能!谢谢你的明确回答。嗯,我们在同一时间提出了几乎完全相同的算法(尽管我使用的是生成器而不是列表)。如果插入的内容都是一次性完成的(如果在同一篇文章的不同时间有许多插入内容,你可能是对的),我不确定一个gapbuffer或rope是否会有帮助。是的。我考虑过发电机,但一开始我决定省略它。然后我看到了你的答案。
def insert_gen(orig_string, insertions):
    prev_key = 0
    for key, value in insertions:
        yield orig_string[prev_key:key] # yield text from the previous insert to this one
        yield value                     # yield the "inserted" text
        prev_key = key
    yield orig_string[prev_key:]        # yield trailing text (after last insert)
parsedContent = "".join(insert_gen(parsedContent, self.trimmedCode))