正则表达式匹配逗号分隔的 key=value 列表,其中 value 可以包含逗号

Posted

技术标签:

【中文标题】正则表达式匹配逗号分隔的 key=value 列表,其中 value 可以包含逗号【英文标题】:Regular expression to match comma separated list of key=value where value can contain commas 【发布时间】:2013-01-16 11:55:33 【问题描述】:

我有一个简单的“解析器”,它只是做一些类似的事情:[x.split('=') for x in mystring.split(',')]

但是 mystring 可以是类似'foo=bar,breakfast=spam,eggs'

显然, 天真的分离器不会这样做。为此,我仅限于 Python 2.6 标准库, 所以比如pyparsing就不能用了。

预期输出为[('foo', 'bar'), ('breakfast', 'spam,eggs')]

我正在尝试使用正则表达式执行此操作,但遇到以下问题:

我的第一次尝试r'([a-z_]+)=(.+),?' 给我[('foo', 'bar,breakfast=spam,eggs')]

显然, 使.+ 不贪婪并不能解决问题。

所以, 我猜我必须以某种方式强制最后一个逗号(或$)。 这样做并没有真正起作用,r'([a-z_]+)=(.+?)(?:,|$)' 就像省略了包含一个的值中逗号后面的内容一样, 例如[('foo', 'bar'), ('breakfast', 'spam')]

我想我必须使用某种后视(?)操作。问题 1. 我使用哪个?或 2. 如何我这样做/这样做?

编辑

根据daramarak下面的回答, 我最终做了与abarnert 几乎相同的事情,后来suggested 做了更详细的形式;

vals = [x.rsplit(',', 1) for x in (data.split('='))]
ret = list()
while vals:
    value = vals.pop()[0]
    key = vals[-1].pop()
    ret.append((key, value))
    if len(vals[-1]) == 0:
        break

编辑 2:

只是为了满足我的好奇心,这真的可以用 pure 正则表达式吗?也就是说,re.findall() 会返回一个 2 元组列表?

【问题讨论】:

你的预期输出是什么? 为什么不用分号 (foo=bar;breakfast=spam,eggs)? @RohitJain 键位于= 的左侧,值位于右侧。在= 的左侧始终是一个键。新的键值对也使用逗号分隔。对吗? mystring.split(',').split('=')?将.split('=') 应用于list 对象? @OscarMederos 对,这不是实际代码,因为它有点混乱。我会解决的。 【参考方案1】:

我能否建议您像以前一样使用拆分操作。但先在等号处分割,然后在最右边的逗号处分割,以形成一个左右字符串列表。

input =
"bob=whatever,king=kong,banana=herb,good,yellow,thorn=hurts"

第一次分裂会变成

first_split = input.split("=")
#first_split = ['bob' 'whatever,king' 'kong,banana' 'herb,good,yellow,thorn' 'hurts']

然后在最右边的逗号处拆分给你:

second_split = [single_word for sublist in first_split for item in sublist.rsplit(",",1)]
#second_split = ['bob' 'whatever' 'king' 'kong' 'banana' 'herb,good,yellow' 'thorn' 'hurts']

然后你只需像这样收集对:

pairs = dict(zip(second_split[::2],second_split[1::2]))

【讨论】:

我认为这可以工作,但你将如何收集对以便它适用于所有输入? 在示例输出中使用分隔符会更容易阅读。从bob whatever king kong banana… 很难判断bob 是一个键,whatever 是一个值,等等。如果我理解你在做什么,最后一步将不起作用,因为你实际上会有@ 987654328@ 对,而不是 (key_n, value_n) 对。但它很接近。看我的回答。 无论谁对此投了反对票,我认为这不值得。有点模糊,但方法显然是正确的,只是缺少最后一步,一旦打印出结果就会很明显。 我在这里的目的一直很模糊,我认为这里没有必要编写代码。我似乎很清楚 OP 能够进行编程,我只是想让 OP 以另一种方式看待问题。 啊,现在我明白你在做什么了。原始版本的问题在于,没有显示逗号或引号或任何其他方式来说明边界在哪里,它是模棱两可的。无论如何,将它展平然后像你一样重新压缩可能比我所做的(x[i][-1], x[i+1][0]) 位更清晰,即使它看起来确实是一个额外的步骤。【参考方案2】:

你可以试试这个,它对我有用:

mystring = "foo=bar,breakfast=spam,eggs,e=a"
n = []
i = 0

for x in mystring.split(','):
    if '=' not in x:
        n[i-1] = "0,1".format(n[i-1], x)
    else:
        n.append(x)
        i += 1
print n

你会得到如下结果:

  ['foo=bar', 'breakfast=spam,eggs', 'e=a']

然后你可以简单地浏览列表并做你想做的事。

【讨论】:

【参考方案3】:

daramarak 的回答要么非常有效,要么按原样工作;很难从示例输出的格式和步骤的模糊描述中分辨出来。但如果它是几乎可以工作的版本,则很容易修复。

将其放入代码中:

>>> bits=[x.rsplit(',', 1) for x in s.split('=')]
>>> kv = [(bits[i][-1], bits[i+1][0]) for i in range(len(bits)-1)]

第一行是(我相信)daramarak 的回答。就其本身而言,第一行为您提供了一对(value_i, key_i+1) 而不是(key_i, value_i)。第二行是最明显的解决方法。通过更多的中间步骤和一些输出,看看它是如何工作的:

>>> s = 'foo=bar,breakfast=spam,eggs,blt=bacon,lettuce,tomato,spam=spam'
>>> bits0 = s.split('=')
>>> bits0
['foo', 'bar,breakfast', 'spam,eggs,blt', 'bacon,lettuce,tomato,spam', 'spam']
>>> bits = [x.rsplit(',', 1) for x in bits0]
>>> bits
[('foo'), ('bar', 'breakfast'), ('spam,eggs', 'blt'), ('bacon,lettuce,tomato', 'spam'), ('spam')]
>>> kv = [(bits[i][-1], bits[i+1][0]) for i in range(len(bits)-1)]
>>> kv
[('foo', 'bar'), ('breakfast', 'spam,eggs'), ('blt', 'bacon,lettuce,tomato'), ('spam', 'spam')]

【讨论】:

澄清了我的答案以表明我的意思。但是您设法使结构变平并一次性配对。那是我的 +1。【参考方案4】:

仅出于比较目的,这里有一个似乎也可以解决问题的正则表达式:

([^=]+)    # key
=          # equals is how we tokenise the original string
([^=]+)    # value
(?:,|$)    # value terminator, either comma or end of string

这里的技巧是限制您在第二组中捕获的内容。 .+ 吞下 = 符号,这是我们可以用来区分键和值的字符。完整的正则表达式不依赖于任何回溯(因此它应该与 re2 之类的东西兼容,如果需要的话)并且可以在 abarnert 的示例中使用。

用法如下:

re.findall(r'([^=]+)=([^=]+)(?:,|$)', 'foo=bar,breakfast=spam,eggs,blt=bacon,lettuce,tomato,spam=spam')

返回:

[('foo', 'bar'), ('breakfast', 'spam,eggs'), ('blt', 'bacon,lettuce,tomato'), ('spam', 'spam')]

【讨论】:

其实,有一次我不得不说,正则表达式方法比其他方法更容易阅读。惊人的! +1【参考方案5】:

假设键的名称从不包含,,当下一个没有,= 的序列由= 接替时,您可以在, 处拆分。

re.split(r',(?=[^,=]+=)', inputString)

(这与我最初的解决方案相同。我希望使用re.split,而不是re.findallstr.split)。

完整的解决方案可以一次性完成:

[re.findall('(.*?)=(.*)', token)[0] for token in re.split(r',(?=[^,=]+=)', inputString)]

【讨论】:

以上是关于正则表达式匹配逗号分隔的 key=value 列表,其中 value 可以包含逗号的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式从逗号分隔列表中删除连续重复(整数和/或浮点数)

正则表达式捕获逗号分隔值

使用正则表达式匹配多个逗号分隔的单词

正则表达式匹配逗号分隔的数字与可选的小数部分

需要 c# 正则表达式将逗号列表中的任何单词与另一个字符串中的任何单词匹配

java 正则表达式匹配字符串,包含没有数字的单词,并且可以选择用逗号分隔