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

2024-10-02 00:38:14 发布

您现在位置: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()将使用更多的内存(因为您基本上得到两个结果列表,一个在|处拆分,另一个在<>处拆分),但是我对正则表达式的Pythons实现知之甚少,无法判断正则表达式将如何执行。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 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
    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

目前,看起来split2 by@duncan胜过了所有其他算法,不管长度如何(至少在这个有限的数据集中如此),而且@mgibsonbr的解决方案也有一些性能问题(很抱歉,但无论如何还是要感谢您的解决方案)。

谢谢大家的意见。


Tags: fromimportinputde解决方案accsplitaha
3条回答

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

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

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

最后,如果您根本不希望创建子字符串(例如,只使用开始和结束索引来节省空间),re.finditer可能会完成此任务。遍历分隔符,并根据找到的分隔符(|<>)对它们之间的文本执行操作。这是一个更复杂的操作,因为你将不得不处理许多角落的情况,但可能是值得的,取决于你的需要。

更新:对于输入格式统一的特定情况,obmarg的解决方案是最好的。如果必须这样做,请将结果后处理为具有嵌套列表:

split_result = re.split( "\||<>", input )
result = [split_result[0], split_result[1], [i for i in split_result[2:] if i]]

(最后一个列表理解是确保在最后一个列表后面没有项目时,您将得到[],而不是['']

更新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

用你的例子测试,结果是:

['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']]

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

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

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

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

我有点惊讶split()在您的代码中执行得如此糟糕,所以我更仔细地看了一下,发现您正在调用内部循环中的list.remove()。另外,您在每个字符串上调用split()额外时间。去掉这些,使用split()的解决方案比regex简单地使用较短的字符串,而在较长的字符串上则是非常接近的第二个解决方案。

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()给出了您想要的嵌套列表,而regex解决方案没有

编辑:我已经更新了这个答案,包括@F1Rumors建议编译regex将提高性能。这确实有一点不同,但是Python会缓存编译的正则表达式,因此保存的内容并不像您所期望的那样多。我认为通常不值得这么做(虽然在某些情况下可能是这样),但通常值得让代码更清晰。

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

相关问题 更多 >

    热门问题