【问题标题】:Match a word, followed by two optionals group in any order匹配一个单词,后跟两个任意顺序的可选组
【发布时间】:2019-01-15 03:10:54
【问题描述】:

我正在为一个小库编写一种解析器。

我的字符串格式如下:

text = "Louis,Edward,John|85.56!26,Billy,Don!18|78.0,Dean"

为了更清楚一点,这是一个姓名人员列表,以逗号分隔,后跟两个可选分隔符(|!),在第一个之后是 weight,它是一个 数字,有 0-2 位小数,而在“!”之后有一个整数表示年龄。分隔符和相关值可以按任何顺序出现,如您在 JohnDon 中看到的那样。

我需要使用正则表达式(我知道我可以通过许多其他方式做到这一点)提取长度在 2 到 4 之间的所有名称以及两个分隔符和以下值(如果存在)。

这是我的预期结果

[('John', '|85.56', '!26'), ('Don', '|78.00' ,'!18'), ('Dean', '', '')]

我正在尝试使用此代码:

import re
text = "Louis,Edward,John|85.56!26,Billy,Don!18|78.0,Dean"
pattern = re.compile(r'(\b\w{2,4}\b)(\!\d+)?(\|\d+(?:\.\d{1,2})?)?')
search_result = pattern.findall(text)
print(search_result)

但这是实际结果:

[('John', '', '|85.56'), ('26', '', ''), ('Don', '!18', '|78.0'), ('Dean', '', '')]

【问题讨论】:

    标签: python regex python-3.x


    【解决方案1】:

    以下正则表达式似乎给出了你想要的:

    re.findall(r'(\b[a-z]{2,4}\b)(?:(!\d+)|(\|\d+(?:\.\d{,2})?))*', text, re.I)
    #[('John', '!26', '|85.56'), ('Don', '!18', '|78.0'), ('Dean', '', '')]
    

    如果您不想要这些名称,可以轻松过滤掉它们。

    【讨论】:

    • 我有 Dill 但没有 Billy,因为长度的关系,它必须在 2 和 4 之间。所以正确的表达式是:pattern = re.compile(r'(\b\w{2 ,4}\b)(?:(!\d+)|(\|\d+\.\d{,2}))*') 谢谢,它有效:)
    • 附带说明,除非您想多次使用它,否则compileing 模式是没有意义的。
    • 我发现您的正则表达式与以下字符串不匹配: text = "Louis,Edward,John|85.56!26,Billy,Don|78,Dean" 这两个分隔符是可选的,可能会出现在两个订单中。因此,对于一个用户,我可以只知道体重,对于另一个用户,我可以只知道年龄,对于另一个用户,我可以同时拥有他们两个人,而对于另一个用户,我可以只知道名字。
    • 好的,只是在 'd{,2})' 后面少了一个问号,因为十进制数字是可选的。现在看起来很完美,谢谢:)
    【解决方案2】:

    Pyparsing 擅长从简单的表达式组合复杂的表达式,并且包含许多用于可选、无序和逗号分隔值的内置函数。请参阅下面代码中的 cmets:

    import pyparsing as pp
    
    real = pp.pyparsing_common.real
    integer = pp.pyparsing_common.integer
    name = pp.Word(pp.alphas, min=2, max=4)
    
    # a valid person entry starts with a name followed by an optional !integer for age
    # and an optional |real for weight; the '&' operator allows these to occur in either
    # order, but at most only one of each will be allowed
    expr = pp.Group(name("name") 
                    + (pp.Optional(pp.Suppress('!') + integer("age"), default='')
                       & pp.Optional(pp.Suppress('|') + real("weight"), default='')))
    
    # other entries that we don't care about
    other = pp.Word(pp.alphas, min=5)
    
    # an expression for the complete input line - delimitedList defaults to using
    # commas as delimiters; and we don't really care about the other entries, just
    # suppress them from the results; whitespace is also skipped implicitly, but that
    # is not an issue in your given sample text
    input_expr = pp.delimitedList(expr | pp.Suppress(other))
    
    # try it against your test data
    text = "Louis,Edward,John|85.56!26,Billy,Don!18|78.0,Dean"                        
    input_expr.runTests(text)
    

    打印:

    Louis,Edward,John|85.56!26,Billy,Don!18|78.0,Dean
    [['John', 85.56, 26], ['Don', 18, 78.0], ['Dean', '', '']]
    [0]:
      ['John', 85.56, 26]
      - age: 26
      - name: 'John'
      - weight: 85.56
    [1]:
      ['Don', 18, 78.0]
      - age: 18
      - name: 'Don'
      - weight: 78.0
    [2]:
      ['Dean', '', '']
      - name: 'Dean'
    

    在这种情况下,使用预定义的实数和整数表达式不仅可以解析值,还可以转换为 int 和 float。命名参数可以像对象属性一样访问:

    for person in input_expr.parseString(text):
        print("({!r}, {}, {})".format(person.name, person.age, person.weight))
    

    给予:

    ('John', 26, 85.56)
    ('Don', 18, 78.0)
    ('Dean', , )
    

    【讨论】:

    • 当我尝试执行你的例子时:AttributeError: module 'pyparsing' has no attribute 'pyparsing_common'
    • 从命令行输入 python -c "import pyparsing; print(pyparsing.__version__)" 来显示你正在运行的 pyparsing 版本。 pyparsing_common 是在 2.1.4 版本中引入的,最新版本是 2.3.1(上周末刚刚发布)。使用pip install pyparsing -U 升级到最新版本。
    • 您有一个名为pyparsing.py 的本地文件吗?这将与已安装的 pyparsing 模块发生冲突。 python -c "import pyparsing; print(pyparsing.__file__)" 将显示您从哪个文件导入。
    • 是的,本地文件名是pyparsing.py。感谢您的耐心等待,并祝贺您的​​图书馆。
    猜你喜欢
    • 2022-01-18
    • 1970-01-01
    • 1970-01-01
    • 2017-12-22
    • 2016-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多