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

新手上路,请多包涵

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

 Item 1 | Item 2 | Item 3 <> Item 4 <> Item 5

解释:这个特殊的例子来自一个列表,其中前两项是标题和日期,而第 3 项到第 5 项将被邀请的人(这些人的数量可以是从 0 到 n 的任何值,其中 n 是数字服务器上的注册用户数)。

据我所知,我有以下选择:

  1. 反复使用 split()
  2. 使用正则表达式和正则表达式函数
  3. 其他一些我还没有想到的Python函数(大概有一些)

对于此示例,解决方案 1 将包括在 | <> 拆分结果列表的最后一个元素,而解决方案 2 可能会产生如下正则表达式:

((.+)|)+((.+)(<>)?)+

好吧,这个正则表达式很糟糕,我自己也能看出来。它也未经测试。但是你明白了。

现在,我正在寻找 a) 花费最少时间和 b) 理想情况下使用最少内存的方式。如果只有两者之一是可能的,我宁愿少点时间。理想的解决方案也适用于有更多项目分隔的字符串 | 和完全没有 <> 的字符串。至少基于正则表达式的解决方案可以做到这一点。

我的理解是 split() 会使用更多内存(因为你基本上得到两个结果列表,一个在 | 分裂,第二个在 <> 分裂), 但我对 Python 对正则表达式的实现知之甚少,无法判断正则表达式的性能如何。 split() 如果涉及到不同数量的项目并且没有第二个分隔符,那么动态性也低于正则表达式。尽管如此,我仍然无法摆脱 Python 可以在没有正则表达式的情况下做得更好的印象,这就是我问的原因。

一些注意事项:

  • 是的,我可以只对这两种解决方案进行基准测试,但我正在尝试了解一些关于 Python 的一般知识以及它在这里的工作原理,如果我只对这两个解决方案进行基准测试,我仍然不知道我错过了哪些 Python 函数。
  • 是的,只有高性能的东西才真正需要在这个级别进行优化,但正如我所说,我正在尝试学习有关 Python 的知识。
  • 另外: 在原来的问题中,我完全忘了提到我需要能够区分由 | 分隔的部分和带有分隔符的部分 <> ,如此简单由 re.split(\||<>,input) 生成的平面列表( 由 obmarg 提议)将不会很好地工作。非常感谢符合此标准的解决方案。

总结一下这个问题:出于什么原因,哪种解决方案最有效?

由于多个请求,我在 split() 解决 方案和 obmarg 提出的第一个正则表达式以及 mgibsonbr 和 duncan 的解决方案上运行了一些时间:

 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 mgibsonbr(input): # Solution by mgibsonbr
    items = re.split(r'\||<>', input) # Split input in items
    offset = 0
    result = [] # The result: strings for regular items, 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
    return result

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

print "mgibs:", timeit.Timer("mgibsonbr('a|b|c|de|f<>ge<>ah')","from __main__ import mgibsonbr").timeit()
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 "mgibs:", timeit.Timer("mgibsonbr('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 mgibsonbr").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 "split:", 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()

结果:

 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 的解决方案也有一些性能问题(对此感到抱歉,但无论如何感谢您的解决方案) .

原文由 malexmave 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 481
2 个回答

我有点惊讶 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())

结果如下:

 split:  1.8427431439631619
split2: 1.0897291360306554
regex:  1.6694280610536225
regex2: 1.2277749050408602
split:  14.356198082969058
split2: 8.009285948995966
regex:  9.526430513011292
regex2: 9.083608677960001

当然 split2() 提供了您想要的嵌套列表,而正则表达式解决方案则没有。

编译正则表达式将提高性能。它确实略有不同,但 Python 缓存了编译后的正则表达式,因此节省的空间并不像您预期的那么多。我认为通常不值得为了速度而这样做(尽管在某些情况下可能值得),但通常值得使代码更清晰。

原文由 Duncan 发布,翻译遵循 CC BY-SA 4.0 许可协议

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

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

我认为它也很有可能比普通的旧拆分更有效(取决于输入数据),因为您需要对第一次拆分的每个字符串输出执行第二次拆分操作,而这不会似乎对记忆或时间都有效。

虽然说过我很容易出错,但唯一可以确定的方法就是计时。

原文由 obmarg 发布,翻译遵循 CC BY-SA 3.0 许可协议

推荐问题