Python中拆分字符串的最有效方法

Python中拆分字符串的最有效方法,python,optimization,split,Python,Optimization,Split,我当前的Python项目将需要大量的字符串拆分来处理传入的包。由于我将在一个相当慢的系统上运行它,我想知道最有效的方法是什么。字符串的格式如下所示: Item 1 | Item 2 | Item 3 <> Item 4 <> Item 5 >>> input = "Item 1 | Item 2 | Item 3 <> Item 4 <> Item 5" >>> re.split( "\||<>",

我当前的Python项目将需要大量的字符串拆分来处理传入的包。由于我将在一个相当慢的系统上运行它,我想知道最有效的方法是什么。字符串的格式如下所示:

Item 1 | Item 2 | Item 3 <> Item 4 <> Item 5
>>> input = "Item 1 | Item 2 | Item 3 <> Item 4 <> Item 5"
>>> re.split( "\||<>", input )
>>> ['Item 1 ', ' Item 2 ', ' Item 3 ', ' Item 4 ', ' Item 5']
结果是:

mgibs: 14.7349407408
split: 6.403942732
split2: 3.68306812233
regex: 5.28414318792
mgibs: 107.046683735
split: 46.0844590775
split2: 26.5595985591
regex: 28.6513302646
目前,@duncan的split2算法似乎胜过了所有其他算法,不管其长度如何(至少在数据集有限的情况下),而且@mgibsonbr的解决方案似乎也存在一些性能问题(对此表示抱歉,但无论如何感谢您提供的解决方案)


谢谢大家的支持。

我不确定这是否是最有效的,但最容易编写的代码似乎是这样的:

Item 1 | Item 2 | Item 3 <> Item 4 <> Item 5
>>> input = "Item 1 | Item 2 | Item 3 <> Item 4 <> Item 5"
>>> re.split( "\||<>", input )
>>> ['Item 1 ', ' Item 2 ', ' Item 3 ', ' Item 4 ', ' Item 5']
>>输入=“项目1 |项目2 |项目3项目4项目5”
>>>关于拆分(“\\\| |”,输入)
>>>[‘项目1’、‘项目2’、‘项目3’、‘项目4’、‘项目5’]
我认为它也很有可能比普通的旧拆分更有效(取决于输入数据),因为您需要对第一次拆分的每个字符串输出执行第二次拆分操作,这似乎对内存或时间都不太有效


尽管我说过我很容易出错,唯一可以确定的方法是计时。

多次调用split可能是不够的,因为它可能会创建不需要的中间字符串。像您建议的那样使用正则表达式是行不通的,因为捕获组将只获取最后一项,而不是每一项。正如obmarg所建议的那样,使用正则表达式进行拆分似乎是最好的方法,假设您正在寻找一个“扁平化”列表

如果您不希望平铺列表,可以先使用正则表达式进行拆分,然后迭代结果,检查原始输入以查看使用了哪个分隔符:

items = re.split(r'\||<>', input)
offset = 0
for i in items:
    delimiter = '|' if input[offset+len(i)] == '|' else '<>'
    offset += len(i) + len(delimiter)
    # Do something with i, depending on whether | or <> was the delimiter
(最后一个列表理解是为了确保如果最后一个
|
之后没有项目,您将获得
[]
而不是
['']

更新2:在阅读了更新后的问题后,我终于明白了您想要实现的目标。下面是使用前面建议的框架的完整示例:

items = re.split(r'\||<>', input) # Split input in items
offset = 0
result = [] # The result: strings for regular itens, lists for <> separated ones
acc = None
for i in items:
    delimiter = '|' if offset+len(i) < len(input) and input[offset+len(i)] == '|' else '<>'
    offset += len(i) + len(delimiter)
    if delimiter == '<>': # Will always put the item in a list
        if acc is None:
            acc = [i] # Create one if doesn't exist
            result.append(acc)
        else:
            acc.append(i)
    else:
        if acc is not None: # If there was a list, put the last item in it
            acc.append(i)
        else:
            result.append(i) # Add the regular items
        acc = None # Clear the list, since what will come next is a regular item or a new list
print result
如果您知道
不会出现在字符串中的其他位置,则可以将“”替换为“|”,后跟单个拆分:

>>> input = "Item 1 | Item 2 | Item 3 <> Item 4 <> Item 5"
>>> input.replace("<>", "|").split("|")
['Item 1 ', ' Item 2 ', ' Item 3 ', ' Item 4 ', ' Item 5']
>>输入=“项目1 |项目2 |项目3项目4项目5”
>>>输入。替换(“,”|“)。拆分(“|”)
[‘项目1’、‘项目2’、‘项目3’、‘项目4’、‘项目5’]
这几乎肯定比进行多次拆分要快。它可能比使用re.split-timeit快,也可能不快。它是你的朋友

编辑: 在具有您提供的示例字符串的系统上,我的版本比re.split快三倍以上:

>>> timeit input.replace("<>", "|").split("|")
1000000 loops, best of 3: 980 ns per loop
>>> import re
>>> timeit re.split(r"\||<>", input)
100000 loops, best of 3: 3.07 us per loop
>>timeit输入。替换(“,“|”)。拆分(“|”)
1000000个循环,最好3个:每个循环980纳秒
>>>进口稀土
>>>timeit重新拆分(r“\\\| |”,输入)
100000个回路,最好为3:3.07 us/回路

(注意,这是使用ipython,它将timeit作为一个内置命令)。

我对
split()
在您的代码中执行得如此糟糕感到有点惊讶,因此我仔细查看了它,注意到您正在内部循环中调用
list.remove()
。另外,您正在对每个字符串额外调用
split()
。除去这些,使用
split()
的解决方案可以轻而易举地解决较短字符串上的正则表达式问题,而较长字符串上的正则表达式就差一秒了

import timeit
import re

def splitit(input):
    res0 = input.split("|")
    res = []
    for element in res0:
        t = element.split("<>")
        if t != [element]:
            res0.remove(element)
            res.append(t)
    return (res0, res)

def split2(input):
    res0 = input.split("|")
    res1, res2 = [], []
    for r in res0:
        if "<>" in r:
            res2.append(r.split("<>"))
        else:
            res1.append(r)
    return res1, res2

def regexit(input):
    return re.split( "\||<>", input )

rSplitter = re.compile("\||<>")

def regexit2(input):
    return rSplitter.split(input)

print("split:", timeit.Timer("splitit('a|b|c|de|f<>ge<>ah')","from __main__ import splitit").timeit())
print("split2:", timeit.Timer("split2('a|b|c|de|f<>ge<>ah')","from __main__ import split2").timeit())
print("regex:", timeit.Timer("regexit('a|b|c|de|f<>ge<>ah')","from __main__ import regexit").timeit())
print("regex2:", timeit.Timer("regexit2('a|b|c|de|f<>ge<>ah')","from __main__ import regexit2").timeit())
print("split:", timeit.Timer("splitit('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import splitit").timeit())
print("split2:", timeit.Timer("split2('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import split2").timeit())
print("regex:", timeit.Timer("regexit('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import regexit").timeit())
print("regex2:", timeit.Timer("regexit2('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import regexit2").timeit())
当然,
split2()

编辑:我已经更新了这个答案,加入了@f1的建议,即编译正则表达式将提高性能。这确实有点不同,但是Python缓存了编译后的正则表达式,因此节省的时间并不像您预期的那么多。我认为通常不值得为了速度而这样做(虽然在某些情况下可能是这样),但让代码更清晰通常是值得的


我还更新了代码,使其在Python 3上运行。

您可以使用替换。首先将
替换为
,然后按
拆分

def replace_way(input):
    return input.replace('<>','|').split('|')

继续,只需使用
timeit
并亲自查看即可。这很容易。我的赌注是
str.split
。我认为对于长字符串,
re
可能更快。但你必须确定基准。我敢打赌这是IO绑定的。我的第一个猜测是,任何类型的拆分都会比它从磁盘或网络接收到的输入更快。如果没有“被邀请的人”,那么您的字符串是像这样的
“Item 1 | Item 2”
,还是像这样的
“Item 1 | Item 2 |”
?@StevenRumbalski是第二个选项。谢谢您的算法。我忘了提到我需要第二部分(项目3-5)作为嵌套列表,以区别于其他部分,并使处理更容易。但是,既然我忘了,我不能真的认为你的解决方案不行;-)@malexmave:一旦你有了一个列表,就很容易了
alist=re.split(\ | |),输入);结果=[alist[0],alist[1],alist[2:]
。瞧。嵌套的。谢谢,如果我在列表中有固定的位置,那肯定会有用的。我在一篇文章中描述的问题呢?你有一个简单的解决办法吗?或者至少有一个比@mgibsonbr提出的方案更简单?谢谢你的努力!谢谢你的意见。我目前正在尝试了解您的
for
块的功能。据我所知,它计算了
|
-分离块结束和
-分离块开始时的偏移量,对吗?不是真的。。。在每次迭代中,
offset
是下一项所在的位置
offset+len(i)
是下一个分隔符(或字符串结尾)的位置。我使用了它,这样您就可以快速找到分隔符,而无需再次扫描输入字符串。但是,如果字符串将具有常规格式,则实际上并不需要它,请参见m
split: 1.8427431439631619
split2: 1.0897291360306554
regex: 1.6694280610536225
regex2: 1.2277749050408602
split: 14.356198082969058
split2: 8.009285948995966
regex: 9.526430513011292
regex2: 9.083608677960001
def replace_way(input):
    return input.replace('<>','|').split('|')
import timeit
import re

def splitit(input):
    res0 = input.split("|")
    res = []
    for element in res0:
        t = element.split("<>")
        if t != [element]:
            res0.remove(element)
            res.append(t)
    return (res0, res)

def regexit(input):
    return re.split( "\||<>", input )

def replace_way(input):
    return input.replace('<>','|').split('|')


print "split:", timeit.Timer("splitit('a|b|c|de|f<>ge<>ah')","from __main__ import splitit").timeit()
print "regex:", timeit.Timer("regexit('a|b|c|de|f<>ge<>ah')","from __main__ import regexit").timeit()
print "replace:",timeit.Timer("replace_way('a|b|c|de|f<>ge<>ah')","from __main__ import replace_way").timeit()
print "split:", timeit.Timer("splitit('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import splitit").timeit()
print "regex:", timeit.Timer("regexit('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import regexit").timeit()
print "replace:",timeit.Timer("replace_way('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import replace_way").timeit()
split: 11.8682055461
regex: 12.7430856814
replace: 2.54299265006
split: 79.2124379066
regex: 68.6917008003
replace: 10.944842347