2015-07-05 1 views
5

Muszę przetworzyć listę prostych ciągów o znanej strukturze, ale uważam, że jest niepotrzebnie niezgrabna. Czuję, że brakuje mi jakiejś sztuczki, może jakiegoś prostego regexu, który uczyniłby to banalnym?Python - łańcuch parse, znana struktura

Łańcuch odnosi się do pewnej liczby lat/miesięcy w przyszłości, chcę zrobić to na dziesiętne lata.

Format Generic: „aYbM”

Jeżeli jest to liczba lat, b jest liczba miesięcy te można ints i oba są opcjonalne (wraz z ich identyfikatorem)

przypadków testowych:

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

Moje próby do tej pory zaangażowany dzielenie ciąg i było dość uciążliwe choć produkują poprawne wyniki:

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 

Odpowiedz

5

Można użyć wyrażenia regularnego wzorca

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

co oznacza

(?:  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 

Przy stosowaniu w

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

metoda groups() zwraca część meczów wewnątrz grupujące nawiasach. None jest zwracana, jeśli grupa nie została dopasowana. Na przykład,

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)) 

daje

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

Uwaga, to nie jest jasne, co powinno się zdarzyć, jeśli wejście do parse_aYbM nie pasuje do wzorca. Z powyższy kod non-meczu podnosi ValueError:

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

ale częściowy mecz może zwrócić wartość:

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

Ściśle rzecz biorąc, "non-match" jest rzeczywiście pasujący łańcuch pusty, ponieważ obie części są opcjonalne. Zwraca 'groups()' as '(None, None)'. To twój kod podnosi wartość ValueError, a nie moduł re. Dobre rozwiązanie. – PaulMcG

+0

Możesz ochronić przed kilkoma miesiącami> = 12 (jak wskazano w oryginalnym pytaniu) za pomocą 'r" (?: (\ D +) Y)? (? :(0? \ D | 1 [01]) M) ? \ b "' - OP nie było jasne, czy wiodące zera mogą być obecne, czy nie. I kończący się "\ b" chroni przed dopasowaniem roku z nieważnym miesiącem. – PaulMcG

+0

Dzięki za szczegółową odpowiedź, wyłapanie tego, co faktycznie robi wyrażenie regex! Znalazłem dokumentację dotyczącą regex, która zakłada pewien poziom wiedzy i jest prawie niemożliwe do odczytania, jeśli go nie ma, więc jest to bardzo pomocne. – David258

0

Można użyć 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 
0

nie zna python regex, ale spróbuj coś takiego (?<year>[^Y])\D(?<month>[^M]*)\D może po prostu rade.