【问题标题】:Python - Parse string, known structurePython - 解析字符串,已知结构
【发布时间】:2015-07-05 01:32:19
【问题描述】:

我必须解析具有已知结构的简单字符串列表,但我发现它不必要地笨重。 我觉得我错过了一个技巧,也许是一些简单的正则表达式会让这变得微不足道?

这个字符串是指未来的若干年/月,我想把它变成小数年。

通用格式:“aYbM”

其中 a 是年数,b 是月数,它们可以是整数,并且都是可选的(连同它们的标识符)

测试用例:

5Y3M == 5.25
5Y == 5.0
6M == 0.5
10Y11M = 10.91666..
3Y14M = raise ValueError("string '%s' cannot be parsed" %input_string)

到目前为止,我的尝试涉及字符串拆分,尽管它们确实产生了正确的结果,但非常麻烦:

def parse_aYbM(maturity_code):
    maturity = 0
    if "Y" in maturity_code:
        maturity += float(maturity_code.split("Y")[0])
        if "M" in maturity_code:
            maturity += float(maturity_code.split("Y")[1].split("M")[0]) / 12
        return maturity
    elif "M" in maturity_code:
        return float(maturity_code[:-1]) / 12
    else:
        return 0 

【问题讨论】:

    标签: python regex string


    【解决方案1】:

    你可以使用正则表达式模式

    (?:(\d+)Y)?(?:(\d+)M)?
    

    意思是

    (?:        start a non-grouping pattern
      (\d+)    match 1-or-more digits, grouped
      Y        followed by a literal Y
    )?         end the non-grouping pattern; matched 0-or-1 times
    (?:        start another non-grouping pattern
      (\d+)    match 1-or-more digits, grouped
      M        followed by a literal M
    )?         end the non-grouping pattern; matched 0-or-1 times 
    

    当用于

    re.match(r'(?:(\d+)Y)?(?:(\d+)M)?', text).groups()
    

    groups() 方法返回分组括号内的匹配部分。如果组不匹配,则返回 None。例如,

    In [220]: re.match(r'(?:(\d+)Y)?(?:(\d+)M)?', '5Y3M').groups()
    Out[220]: ('5', '3')
    
    In [221]: re.match(r'(?:(\d+)Y)?(?:(\d+)M)?', '3M').groups()
    Out[221]: (None, '3')
    

    import re
    def parse_aYbM(text):
        a, b = re.match(r'(?:(\d+)Y)?(?:(\d+)M)?', text).groups()
        if a == b == None:
            raise ValueError('input does not match aYbM')
        a, b = [int(item) if item is not None else 0 for item in (a, b)]
        return a + b/12.0
    
    tests = [
    ('5Y3M', 5.25),
    ('5Y', 5.0),
    ('6M', 0.5),
    ('10Y11M', 10.917),
    ('3Y14M', 4.167),
    ]
    
    for test, expected in tests:
        result = parse_aYbM(test)
        status = 'Failed'
        if abs(result - expected) < 0.001:
            status = 'Passed'
        print('{}: {} --> {}'.format(status, test, result))
    

    产量

    Passed: 5Y3M --> 5.25
    Passed: 5Y --> 5.0
    Passed: 6M --> 0.5
    Passed: 10Y11M --> 10.9166666667
    Passed: 3Y14M --> 4.16666666667
    

    请注意,如果parse_aYbM 的输入与模式不匹配会发生什么情况尚不清楚。上面的代码不匹配引发ValueError

    In [227]: parse_aYbM('foo')
    ValueError: input does not match aYbM
    

    但部分匹配可能会返回一个值:

    In [229]: parse_aYbM('0Yfoo')
    Out[229]: 0.0
    

    【讨论】:

    • 严格来说,您的“不匹配”实际上是匹配空字符串,因为这两个部分都是可选的。这会将groups() 返回为(None, None)。引发 ValueError 的是您的代码,而不是 re 模块。不过很好的解决方案。
    • 您可以使用r"(?:(\d+)Y)?(?:(0?\d|1[01])M)?\b" 防止几个月 >= 12(如原始问题所示) - OP 不清楚是否存在前导零。尾随 \b 防止将前导年份与无效月份匹配。
    • 感谢您的详细回答,打破了正则表达式的实际作用!我发现有关正则表达式的文档假定您具有一定的知识水平,如果您不在那里,几乎不可能阅读,所以这真的很有帮助。
    【解决方案2】:

    您可以使用re.findall

    >>> def parse(m):
        s = 0
        j = re.findall(r'\d+Y|\d+M', m)
        for i in j:
            if 'Y' in i:
                s += float(i[:-1])
            if 'M' in i:
                s += float(i[:-1])/12
        print(s)
    
    
    >>> parse('5Y')
    5.0
    >>> parse('6M')
    0.5
    >>> parse('10Y11M')
    10.916666666666666
    >>> parse('3Y14M')
    4.166666666666667
    

    【讨论】:

      【解决方案3】:

      不熟悉 python 正则表达式,但尝试类似 (?&lt;year&gt;[^Y])\D(?&lt;month&gt;[^M]*)\D可能会成功。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-01-01
        • 2021-05-03
        • 1970-01-01
        • 2011-04-19
        • 1970-01-01
        • 2018-10-20
        • 1970-01-01
        • 2022-01-11
        相关资源
        最近更新 更多