在给定字符的第n个出现位置拆分字符串

55

有没有一种Python的方法可以在给定分隔符的第n次出现后拆分字符串?

假设有一个字符串:

'20_231_myString_234'

它应该被拆分成两部分(以'_'为分隔符,在第二次出现后):

['20_231', 'myString_234']

或者说实现这一点的唯一方法是计数、分割和连接吗?


那么你的意思是你想在字符串中从开头算起,在第二个下划线处分割字符串? - Jude Osborn
是的。抱歉,刚才表达不太清楚。 - cherrun
9个回答

77
>>> n = 2
>>> groups = text.split('_')
>>> '_'.join(groups[:n]), '_'.join(groups[n:])
('20_231', 'myString_234')

看起来这是最易读的方法,另一种方法是使用正则表达式。


2
我认为这种方法比正则表达式更清晰,但正则表达式也不错。谢谢。 - cherrun
有没有一种方法可以将这个答案以数据框的格式使用?其中Column1 = String,输出到Column2和3? - Arthur D. Howland
我建议阅读我的答案。我认为它更易读,而且不是正则表达式。 - Yuval
虽然这仍然是未知数量出现次数的最佳答案,但当我们想要第一次或最后一次出现时,有更好的解决方案。对于第一次出现,我们应该使用_partition_。对于最后一次出现,我们应该使用_rpartition_。这不仅更易读,而且性能更好,因为我们避免了不必要的拆分和连接。我希望Python将来会添加一个可选的“occurrence=1”参数,以适应所有情况。 - Leandro 86
@Leandro86 正如您所提到的,分割仅在第一次出现时拆分,因此如果您的意思是向 str.split 添加可选参数,则已经存在该参数,即 maxsplit https://docs.python.org/3/library/stdtypes.html#str.rsplit - jamylak

9

使用re获取一个形如^((?:[^_]*_){n-1}[^_]*)_(.*)的正则表达式,其中n是一个变量:

n=2
s='20_231_myString_234'
m=re.match(r'^((?:[^_]*_){%d}[^_]*)_(.*)' % (n-1), s)
if m: print m.groups()

或者有一个好的功能:
import re
def nthofchar(s, c, n):
    regex=r'^((?:[^%c]*%c){%d}[^%c]*)%c(.*)' % (c,c,n-1,c,c)
    l = ()
    m = re.match(regex, s)
    if m: l = m.groups()
    return l

s='20_231_myString_234'
print nthofchar(s, '_', 2)

或者不使用正则表达式,采用迭代查找的方法:

def nth_split(s, delim, n): 
    p, c = -1, 0
    while c < n:  
        p = s.index(delim, p + 1)
        c += 1
    return s[:p], s[p + 1:] 

s1, s2 = nth_split('20_231_myString_234', '_', 2)
print s1, ":", s2

使用您的代码,输出为:('20_231_', 'myString_234')。分隔符也包含在内。 - cherrun
@cherrun 在正则表达式中的 (.*) 前插入分隔符。 - jamylak
@perreal 在这种特定情况下,可以随意使用旧的字符串格式化,这样就不需要所有的 {{}} - jamylak
1
@perreal 现在看起来非常不错。 - jamylak

6

我喜欢这个解决方案,因为它不需要使用任何实际的正则表达式,并且可以轻松地适应另一个“nth”或分隔符。

import re

string = "20_231_myString_234"
occur = 2  # on which occourence you want to split

indices = [x.start() for x in re.finditer("_", string)]
part1 = string[0:indices[occur-1]]
part2 = string[indices[occur-1]+1:]

print (part1, ' ', part2)

5

我想提供我的意见。 split() 的第二个参数允许您在一定数量的字符串后限制分割:

def split_at(s, delim, n):
    r = s.split(delim, n)[n]
    return s[:-len(r)-len(delim)], r

在我的电脑上,@perreal提供的两种优秀答案(迭代查找和正则表达式)实际上比这种方法慢1.4倍和1.6倍(分别)。

值得注意的是,如果你不需要初始部分,它甚至可以更快。然后代码变成:

def remove_head_parts(s, delim, n):
    return s.split(delim, n)[n]

我承认对于这个名称并不太确定,但它确实完成了工作。令人惊讶的是,它比迭代查找快2倍,比正则表达式快3倍。

我在我的测试脚本上线了。欢迎您进行评论和审查。


1
如@Yuval在他的回答中指出,以及@jamylak在他的回答中评论的那样,split和rsplit方法接受第二个(可选)参数maxsplit,以避免进行不必要的分割。因此,我发现更好的解决方案(无论是可读性还是性能)是这样的:
s = '20_231_myString_234'
first_part = text.rsplit('_', 2)[0] # Gives '20_231'
second_part = text.split('_', 2)[2] # Gives 'myString_234'

这不仅简单,而且避免了正则表达式解决方案和其他使用 join 撤消不必要拆分的性能损失。


1
>>>import re
>>>str= '20_231_myString_234'

>>> occerence = [m.start() for m in re.finditer('_',str)]  # this will give you a list of '_' position
>>>occerence
[2, 6, 15]
>>>result = [str[:occerence[1]],str[occerence[1]+1:]] # [str[:6],str[7:]]
>>>result
['20_231', 'myString_234']

0
在 @AllBlackt 解决方案的函数形式中
def split_nth(s, sep, n):
    n_split_groups = []
    groups = s.split(sep)
    while len(groups):
          n_split_groups.append(sep.join(groups[:n]))
          groups = groups[n:]
    return n_split_groups

s = "aaaaa bbbbb ccccc ddddd eeeeeee ffffffff"
print (split_nth(s, " ", 2))

['aaaaa bbbbb', 'ccccc ddddd', 'eeeeeee ffffffff']

0

我有一个更大的字符串需要每n个字符拆分一次,最终使用了以下代码:

# Split every 6 spaces
n = 6
sep = ' '
n_split_groups = []

groups = err_str.split(sep)
while len(groups):
    n_split_groups.append(sep.join(groups[:n]))
    groups = groups[n:]

print n_split_groups

感谢 @perreal!


0

这取决于您对此拆分的模式是什么。因为如果前两个元素总是数字,例如,您可以构建re模块并使用正则表达式。它也能够拆分您的字符串。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接