正则表达式匹配逗号分隔的 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.findall
或str.split
)。
完整的解决方案可以一次性完成:
[re.findall('(.*?)=(.*)', token)[0] for token in re.split(r',(?=[^,=]+=)', inputString)]
【讨论】:
以上是关于正则表达式匹配逗号分隔的 key=value 列表,其中 value 可以包含逗号的主要内容,如果未能解决你的问题,请参考以下文章