【问题标题】:Python split string by multiple delimiters following a hierarchyPython由多个分隔符按照层次结构拆分字符串
【发布时间】:2021-08-07 00:32:42
【问题描述】:

我只想根据多个分隔符(如“and”、“&”和“-”)将字符串拆分一次。示例:

'121 34 adsfd' -> ['121 34 adsfd']
'dsfsd and adfd' -> ['dsfsd ', ' adfd']
'dsfsd & adfd' -> ['dsfsd ', ' adfd']
'dsfsd - adfd' -> ['dsfsd ', ' adfd']
'dsfsd and adfd and adsfa' -> ['dsfsd ', ' adfd and adsfa']
'dsfsd and adfd - adsfa' -> ['dsfsd ', ' adfd - adsfa']
'dsfsd - adfd and adsfa' -> ['dsfsd - adfd ', ' adsfa']

我尝试了以下代码来实现这一点:

import re
re.split('and|&|-', string, maxsplit=1)

它适用于除最后一种情况之外的所有情况。由于它不遵循层次结构,因此它返回的最后一个:

'dsfsd - adfd and adsfa' -> ['dsfsd ', ' adfd and adsfa']

我怎样才能做到这一点?

【问题讨论】:

  • 我不确定正则表达式是否支持这种类型的操作(我会很高兴并且有兴趣被证明是错误的!)。也许您可以考虑使用 parsy 之类的东西构建语法。
  • 也许您可以尝试在'and' 上拆分,如果没有拆分,请尝试在'&' 上拆分,然后尝试'-'。不是最优雅的,但它可以完成工作。
  • 问题是,正则表达式解析器需要从 ^ 开始重新开始,以实现您的订单。此外,需要消耗初始部分。如果你可以使用PyPI regex,一个通用的想法是res = regex.split(r"^(?:.*?\Kand|.*?\K&|.*?\K-)", s)

标签: python regex string


【解决方案1】:

这对于单个正则表达式是不切实际的。你可以让它与负面的lookbehinds一起工作,但每增加一个分隔符就会变得相当复杂。用普通的旧 str.split() 和多行来做到这一点非常简单。您所要做的就是检查使用当前分隔符拆分是否会给您两个元素。如果是这样,这就是你的答案。如果没有,请转到下一个分隔符:

def split_new(inp, delims):
    for d in delims:
        result = inp.split(d, maxsplit=1)
        if len(result) == 2: return result

    return [inp] # If nothing worked, return the input

对此进行测试:

teststrs = ['121 34 adsfd' , 'dsfsd and adfd', 'dsfsd & adfd' , 'dsfsd - adfd' , 'dsfsd and adfd and adsfa' , 'dsfsd and adfd - adsfa' , 'dsfsd - adfd and adsfa' ]
for t in teststrs:
    print(repr(t), '->', split_new(t, ['and', '&', '-']))

输出

'121 34 adsfd' -> ['121 34 adsfd']
'dsfsd and adfd' -> ['dsfsd ', ' adfd']
'dsfsd & adfd' -> ['dsfsd ', ' adfd']
'dsfsd - adfd' -> ['dsfsd ', ' adfd']
'dsfsd and adfd and adsfa' -> ['dsfsd ', ' adfd and adsfa']
'dsfsd and adfd - adsfa' -> ['dsfsd ', ' adfd - adsfa']
'dsfsd - adfd and adsfa' -> ['dsfsd - adfd ', ' adsfa']

【讨论】:

  • 简单、易读且易于添加更多分隔符。
  • 我也会这样做。不过this 是另一种表达方式,它更好,至少对我而言。
  • 这个。比已接受答案中的正则表达式好得多,如果您必须在一年后对其进行修改,那会让您讨厌自己。
【解决方案2】:

试试:

import re

tests = [
    ["121 34 adsfd", ["121 34 adsfd"]],
    ["dsfsd and adfd", ["dsfsd ", " adfd"]],
    ["dsfsd & adfd", ["dsfsd ", " adfd"]],
    ["dsfsd - adfd", ["dsfsd ", " adfd"]],
    ["dsfsd and adfd and adsfa", ["dsfsd ", " adfd and adsfa"]],
    ["dsfsd and adfd - adsfa", ["dsfsd ", " adfd - adsfa"]],
    ["dsfsd - adfd and adsfa", ["dsfsd - adfd ", " adsfa"]],
]

for s, result in tests:
    res = re.split(r"and|&(?!.*and)|-(?!.*and|.*&)", s, maxsplit=1)
    print(res)
    assert res == result

打印:

['121 34 adsfd']
['dsfsd ', ' adfd']
['dsfsd ', ' adfd']
['dsfsd ', ' adfd']
['dsfsd ', ' adfd and adsfa']
['dsfsd ', ' adfd - adsfa']
['dsfsd - adfd ', ' adsfa']

解释:

正则表达式 and|&(?!.*and)|-(?!.*and|.*&) 使用 3 种替代方法。

  1. 我们总是匹配 and 或者:
  2. 仅当前面没有 and 时,我们才匹配 &(使用负前瞻 (?! ) 或:
  3. 只有在前面没有 and& 时,我们才会匹配 -

我们在re.sub 中使用此模式 -> 仅在第一次匹配时拆分。

【讨论】:

  • 良好且相对较短的模式。
  • @AndrejKesely 谢谢!这对我有用。你能解释一下它是如何工作的吗?
  • @Ank 正则表达式正在使用负前瞻 (?! ) - 只有当前面没有 and& 时,我们才会匹配 -
  • 循环中使用的正则表达式应该在循环之前编译。总时间将减少约 25%。
【解决方案3】:

您可以保留按值排序的分隔符列表。然后,您可以将re.splitre.findall 结合使用,根据ops 中的排名,仅使用后者产生的在拆分中价值最低的分隔符:

import re
def split_order(s):
   r, ops = re.findall('(?<=\s)and(?=\s)|\&|\-', s), ['and', '&', '-']
   m = -1 if not r else min([ops.index(i) for i in r])
   a, *b = re.split('|'.join(l:=[i for i in r if ops.index(i) == m]), s)
   return [s] if not l else ([a] if not b else [a, s[len(a)+len(l[0]):]])


vals = ['121 34 adsfd' , 'dsfsd and adfd', 'dsfsd & adfd' , 'dsfsd - adfd' , 'dsfsd and adfd and adsfa' , 'dsfsd and adfd - adsfa' , 'dsfsd - adfd and adsfa' ]
for i in vals:
   print(split_order(i))

输出:

['121 34 adsfd']
['dsfsd ', ' adfd']
['dsfsd ', ' adfd']
['dsfsd ', ' adfd']
['dsfsd ', ' adfd and adsfa']
['dsfsd ', ' adfd - adsfa']
['dsfsd - adfd ', ' adsfa']

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多