如何从字符串的开头删除到第二个特定字符?

时间:2015-01-18 04:54:03

标签: python string

我有一堆形式的字符串:

'foo.bar.baz.spam.spam.spam...etc'

很可能他们有三个或更多由.分隔的多字母子串。可能存在格式错误的字符串少于两个.,在这种情况下我想要原始字符串。

我想到的第一件事就是str.partition方法,如果我在第一个.之后完成所有操作,我会使用它:

'foo.bar.baz.boink.a.b.c'.partition('.')[2]

返回

'bar.baz.boink.a.b.c'

这可以重复:

def secondpartition(s):
    return s.partition('.')[2].partition('.')[2] or s

但这有效吗?调用方法两次并使用下标两次似乎没有效率。它肯定是不优雅的。有没有更好的办法?

主要问题是:

  

如何删除.字符的开头到第二个实例的所有内容,以便'foo.bar.baz.spam.spam.spam'成为'baz.spam.spam.spam'?什么是最好/最有效的方法呢?

2 个答案:

答案 0 :(得分:3)

str.splitmaxsplit参数一起使用:

>>> 'foo.bar.baz.spam.spam.spam'.split('.', 2)[-1]
'baz.spam.spam.spam'

<强>更新

处理少于​​两个. s的字符串:

def secondpartition(s):
    parts = s.split('.', 2)
    if len(parts) <= 2:
        return s
    return parts[-1]

答案 1 :(得分:0)

摘要:这是最高效的方法(一般化为n个字符):

def maxsplittwoexcept(s, n, c):
    '''
    given string s, return the string after the nth character c
    if less than n c's, return the whole string s.
    '''
    try:
        return s.split(c, 2)[2]
    except IndexError:
        return s

但我展示了其他比较方法。

使用字符串方法和正则表达式可以有多种方法。我将确保您能够按顺序剪切和粘贴所有内容,从而跟随口译员。

首先进口:

import re
import timeit
from itertools import islice

不同方法:字符串方法

问题中提到的方法是分区两次,但我打了折扣,因为它似乎相当不优雅且不必要地重复:

def secondpartition(s):
    return s.partition('.')[2].partition('.')[2] or s

想到这样做的第二种方法是分开.,从第二个开始切片,然后加入.&#39}。这让我觉得相当优雅,我认为它会相当高效。

def splitslicejoin(s):
    return '.'.join(s.split('.')[2:]) or s

但切片创建了一个不必要的额外列表。但是,来自itertools模块的islice提供了一个不可复制的迭代!所以我希望这会做得更好:

def splitislicejoin(s):
    return '.'.join(islice(s.split('.'), 2, None)) or s

不同的方法:正则表达式

现在正则表达式。使用正则表达式首先想到的方法是使用空字符串查找并替换第二个.

dot2 = re.compile('.*?\..*?\.')
def redot2(s):
    return dot2.sub('', s)

但我发现使用非捕获组可能会更好,并在最后返回一个匹配:

dot2match = re.compile('(?:.*?\..*?\.)(.*)')
def redot2match(s):
    match = dot2match.match(s)
    if match is not None:
        return match.group(1)
    else:
        return s

最后,我可以使用正则表达式搜索来查找第二个.的结尾,然后使用该索引对字符串进行切片,这将使用更多代码,但可能仍然快速且内存效率高

dot = re.compile('\.')
def find2nddot(s):
    for i, found_dot in enumerate(dot.finditer(s)):
        if i == 1:
            return s[found_dot.end():] or s
    return s

更新 Falsetru建议str.split的maxsplit参数,这完全让我不知所措。我的想法是它可能是最简单的方法,但是分配和额外的检查可能会伤害它。

def maxsplittwo(s):
    parts = s.split('.', 2)
    if len(parts) <= 2:
        return s
    return parts[-1]

JonClements建议使用引用https://stackoverflow.com/a/27989577/541136的引用,如下所示:

def maxsplittwoexcept(s):
    try:
        return s.split('.', 2)[2]
    except IndexError:
        return s

这是完全合适的,因为没有足够的. s将是例外。

<强>测试

现在让我们测试一下我们的功能。首先,让我们声明它们实际工作(不是生产代码中的最佳实践,它应该使用单元测试,但对StackOverflow上的快速验证很有用):

functions = ('secondpartition', 'redot2match', 'redot2', 
             'splitslicejoin', 'splitislicejoin', 'find2nddot',
             'maxsplittwo', 'maxsplittwoexcept')

for function in functions:
    assert globals()[function]('foo.baz') == 'foo.baz'
    assert globals()[function]('foo.baz.bar') == 'bar'
    assert globals()[function]('foo.baz.bar.boink') == 'bar.boink'

断言不会提升AssertionError,所以现在让他们看看他们的表现如何:

<强>性能

setup = 'from __main__ import ' + ', '.join(functions)

perfs = {}
for func in functions:
    perfs[func] = min(timeit.repeat(func + '("foo.bar.baz.a.b.c")', setup))

for func in sorted(perfs, key=lambda x: perfs[x]):
    print('{0}: {1}'.format(func, perfs[func]))

<强>结果

更新最佳表现者是false maxsplittwo,它略微超出了第二个分区的功能。恭喜你。这是有道理的,因为它是一种非常直接的方法。 JonClements的修改甚至更好......

maxsplittwoexcept: 1.01329493523
maxsplittwo: 1.08345508575
secondpartition: 1.1336209774
splitslicejoin: 1.49500417709
redot2match: 2.22423219681
splitislicejoin: 3.4605550766
find2nddot: 3.77172589302
redot2: 4.69134306908

较早的运行并且没有使用falsetru的maxsplittwo和JonClements&#39; maxsplittwoexcept:

secondpartition: 0.636116637553
splitslicejoin: 1.05499717616
redot2match: 1.10188927335
redot2: 1.6313087087
find2nddot: 1.65386564664
splitislicejoin: 3.13693511439

事实证明,大多数表现方法是分区两次,即使我的直觉并不喜欢它。

另外,事实证明我对使用islice的直觉在这种情况下是错误的,它的性能要差得多,因此如果遇到类似的代码,那么常规切片中的额外列表可能值得权衡。

在正则表达式中,我所需字符串的匹配方法在这里表现最佳,几乎与splitslicejoin绑定。