10

我有一个天真的“解析器”,它只是做类似的事情:
[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后来以稍微冗长的形式提出的几乎相同的事情;

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:

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

4

5 回答 5

11

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

([^=]+)    # 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')]
于 2013-02-01T10:55:26.337 回答
4

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')]
于 2013-02-01T08:14:01.253 回答
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]))
于 2013-02-01T07:53:06.140 回答
0

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

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']

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

于 2013-02-01T08:12:18.057 回答
0

假设键的名称从不包含,您可以在下一个不包含and的序列时,拆分。,,==

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

(这和我原来的解决方案一样。我希望re.split被使用,而不是re.findallor str.split)。

完整的解决方案可以单行完成:

[re.findall('(.*?)=(.*)', token)[0] for token in re.split(r',(?=[^,=]+=)', inputString)]
于 2013-02-01T13:23:50.167 回答