使用Python中的正则表达式对数据进行分组

时间:2016-01-20 09:42:58

标签: python regex

我有一些像这样的原始数据:

Dear   John    Buy   1 of Coke, cost 10 dollars
       Ivan    Buy  20 of Milk
Dear   Tina    Buy  10 of Coke, cost 100 dollars
       Mary    Buy   5 of Milk

数据规则是:

  • 不是每个人都会以"亲爱的"开头,如果有,则必须以费用结束

  • 该项目可能并不总是正常的单词,可以无限制地写入(包括str,num等)

我想对信息进行分组,并尝试使用正则表达式。那是我以前尝试过的:

for line in file.readlines():
    match = re.search(r'\s+(?P<name>\w+)\D*(?P<num>\d+)\sof\s(?P<item>\w+)(?:\D+)(?P<costs>\d*)',line)
    if match is not None:
        print(match.groups())
file.close()

现在输出如下:

('John', '1', 'Coke', '10')
('Ivan', '20', 'Milk', '')
('Tina', '10', 'Coke', '100')
('Mary', '5', 'Milk', '')

显示以上内容是我想要的。但是,如果item被某些奇怪的字符串替换为A1~A10,则某些输出会收到错误信息:

('Ivan', '20', 'A1', '10')
('Mary', '5', 'A1', '10')

我认为item field中的常量格式是它始终以,结尾(如果有的话)。但我只是不知道如何利用这一优势。

通过使用上面的代码认为它暂时成功,我认为(?P<item>\w+)必须像(?P<item>.+)一样被替换。如果我这样做,它会在元组中使用错误的字符串,如:

('John', '1', 'Coke, cost 10 dollars', '')

如何使用Python中的正则表达式将数据读入我想要的格式?

4 个答案:

答案 0 :(得分:5)

我会使用这个regex

r'\s+(?P<name>\w+)\D*(?P<num>\d+)\sof\s(?P<item>[^,]+)(?:,\D+)?(?P<costs>\d+)?'

<强>演示

>>> line = 'Dear   Tina    Buy  10 of A1~A10'
>>> match = re.search(r'\s+(?P<name>\w+)\D*(?P<num>\d+)\sof\s(?P<item>[^,]+)(?:,\D+)?(?P<costs>\d+)?', line)
>>> match.groups()
('Tina', '10', 'A1~A10', None)

>>> line = 'Dear   Tina    Buy  10 of A1~A10, cost 100 dollars'
>>> match = re.search(r'\s+(?P<name>\w+)\D*(?P<num>\d+)\sof\s(?P<item>[^,]+)(?:,\D+)?(?P<costs>\d+)?', line)
>>> match.groups()
('Tina', '10', 'A1~A10', '100')

<强>解释

你的正则表达式的第一部分非常好,这是一个棘手的部分:

(?P<item>[^,]+)我们确定当 cost 字符串存在时,该字符串将包含逗号,这里我们说我们想要< em>除逗号以外的任何内容来设置项目值。

(?:,\D+)?(?P<costs>\d+)?我们在这里使用两组。 重要的事情是围绕群组的括号之后的?

  

&#39;&#39;导致生成的RE匹配0或1次重​​复   在RE之前。 AB?将匹配'a'或'ab'。

因此我们使用?来匹配两种可能性( cost 字符串是否存在)

(?:,\D+)是一个non-capturing,它会匹配逗号,后跟除了数字之外的任何内容。

(?P<costs>\d+)将捕获指定组费用中的任何数字。

答案 1 :(得分:5)

我试过这个正则表达式

^(Dear)?\s*(?P<name>\w*)\D*(?P<num>\d+)\sof\s(?P<drink>\w*)(,\D*(?P<cost>\d+)\D*)?

解释

  1. ^(Dear)?匹配行从Dear开始,如果存在
  2. (?P<name>\w*)名称捕获组以捕获名称
  3. \D*匹配任何非数字字符
  4. (?P<num>\d+)命名捕获组以获取num
  5. \sof\s匹配字符串of
  6. (?P<drink>\w*)来喝酒
  7. (,\D*(?P<cost>\d+)\D*)?这是一个可选的小组来获取饮料的费用
  8. >>> reobject = re.compile('^(Dear)?\s*(\w*)[\sa-zA-Z]*(\d+)\s*\w*\s*(\w*)(,[\sa-zA-Z]*(\d+)[\s\w]*)?')
    

    第一个数据摘录

    >>> data1 = 'Dear   John    Buy   1 of Coke, cost 10 dollars'
    >>> match_object = reobject.search(data1)
    >>> print (match_object.group('name') , match_object.group('num'), match_object.group('drink'), match_object.group('cost'))
    ('John', '1', 'Coke', '10')
    

    第二个数据摘录

    >>> data2 = '       Ivan    Buy  20 of Milk'
    >>> match_object = reobject.search(data2)
    >>> print (match_object.group('name') , match_object.group('num'), match_object.group('drink'), match_object.group('cost'))
    ('Ivan', '20', 'Milk', None)
    

答案 2 :(得分:5)

没有正则表达式:

with open('commandes.txt') as f:
    results = []
    for line in f:
        parts = line.split(None, 5)
        price = ''
        if parts[0] == 'Dear':
            tmp = parts[5].split(',', 1)
            for tok in tmp[1].split():
                if tok.isnumeric():
                    price = tok
                    break 
            results.append((parts[1], parts[3], tmp[0], price))
        else:
            results.append((parts[0], parts[2], parts[4].split(',')[0], price))
    print(results)

在产品名称之前,除了空格之外,不关心使用哪些字符,这就是为什么每一行被5个部分的空格分割的原因。当该行以&#34; Dear&#34;开头时,最后一部分用逗号分隔以提取产品名称和价格。请注意,如果价格始终位于同一位置(即:&#34;成本&#34;),您可以避开最里面的for循环并将其替换为price = tmp[1].split()[1]

注意:如果要防止处理空行,可以将第一个for循环更改为:

for line in (x for x in f if x.rstrip()):

答案 3 :(得分:3)

如果您使用.+,则子模式将抓取整个行的其余部分,因为.匹配任何字符,但没有re.S标记的换行符。

您可以将\w+替换为否定字符类子模式[^,]+,以匹配逗号以外的一个或多个字符:

r'\s+(?P<name>\w+)\D*(?P<num>\d+)\sof\s(?P<item>[^,]+)\D*(?P<costs>\d*)'
                                                ^^^^^

请参阅IDEONE demo

import re
file = "Dear   John    Buy   1 of A1~A10, cost 10 dollars\n       Ivan    Buy  20 of Milk\nDear   Tina    Buy  10 of Coke, cost 100 dollars\n       Mary    Buy   5 of Milk"
for line in file.split("\n"):
    match = re.search(r'\s+(?P<name>\w+)\D*(?P<num>\d+)\sof\s(?P<item>[^,\W]+)\D*(?P<costs>\d*)',line)
    if match:
        print(match.groups())

输出:

('John', '1', 'A1~A10', '10')
('Ivan', '20', 'Mil', '')
('Tina', '10', 'Coke', '100')
('Mary', '5', 'Mil', '')