2013-08-25 1 views
0

Я хочу написать функцию:Python: соответствие строки со строками модели с номером, строки и даты заполнителей с хорошей ошибкой ловли

match_string(input, pattern, valid_words, date_format) 

input является нормальной строкой. pattern - это строка, которая имеет заполнители для чисел, слов и дат. Например, «# является номером ## является строкой и ### является датой». Здесь я использовал #, # и ### для обозначения числа, строки и даты, соответственно, но я не привязан к какому-либо конкретному представлению заполнителя.

match_string должен возвращать true, если input «соответствует» шаблону; то есть, если в нем есть номера, где есть номера заполнителей, слова, в которых есть слова заполнители и слово находится в данном списке valid_words, а также даты, когда есть дата-заполнители, а дата указана в данном date_format.

И, наконец, мне нужна match_string для возврата подробной информации об ошибке; если input не соответствует, и это был один из заполнителей, он должен сказать «не число», «не в списке слов», или «не дата». Если он не соответствует нормальным частям шаблона, он должен просто ошибиться или вернуть False

Это много, чтобы спросить :), но вопрос заключается в том, как реализовать match_string. Я вижу регулярные выражения и строковые форматы, и пользовательские определения ошибок должны мне помочь, но у меня проблемы с этим. Я надеюсь, что этот вопрос может помочь другим, продемонстрировав гибкое использование регулярных выражений в python.

Примеры:

> match_string('1 is a number foo is a string 12-1-2013 is a date', '# is a number ## is a string ### is a date', [foo], '%m-%d-%y') 
True 

> match_string('foo is a number bar is a string 12-1-2013 is a date', '# is a number ## is a string ### is a date', [foo], '%m-%d-%y') 
Error: number expected for 'foo' 

> match_string('1 is a number bar is a string 12-1-2013 is a date', '# is a number ## is a string ### is a date', [foo], '%m-%d-%y') 
Error: invalid word: bar 

> match_string('1 is a number foo is a string January is a date', '# is a number ## is a string ### is a date', [foo], '%m-%d-%y') 
Error: invalid date format January 

Заранее спасибо!

+0

Теперь мы знаем, что вы хотите и что вам нужно. Но каков ваш вопрос? – Hyperboreus

+0

Как должно быть реализовано 'match_string'? – Neil

+1

Вы должны, по крайней мере, включать несколько примерных входов и ожидаемых выходов для разных выходов. – Akinakes

ответ

2

Я думаю, что было бы более эффективно либо напрямую манипулировать строкой, либо просто модифицировать один шаблон регулярного выражения. К сожалению, позже будет возвращаться только истина или ложь.

Как бы то ни было, я создал сценарий по запросу, он будет принимать 31 день в каждом месяце, хотя это должно быть довольно легко добавить дополнительные ограничения.

from re import sub, match 

def match_string(input, pattern, valid_words, date_format): 
    errors = [] 

    # makes sure that input and pattern are compatible 
    regex_pattern = sub(r'#{1,3}', '(.+?)', pattern) 
    if not match(regex_pattern, input): 
     return 'Error: Input doesn\'t match pattern!' 

    # converts the data_format to a regex 
    date_regex = sub(r'%d', '(?P<day>\d+)', date_format) 
    date_regex = sub(r'%m', '(?P<month>\d+)', date_regex) 
    date_regex = sub(r'%y', '(?P<year>\d+)', date_regex) 

    # extracts the dates 
    regex_pattern = sub(r'###', '(.+?)', pattern) 
    regex_pattern = sub(r'##', '(?:.+?)', regex_pattern) 
    regex_pattern = sub(r'#', '(?:.+?)', regex_pattern) 
    for date in match(regex_pattern, input).groups(): 
     m = match(date_regex, date) 
     if not m: 
      errors.append('Error: %s is not a valid date!' % date) 
     else: 
      if int(m.group('day')) < 1 or 31 < int(m.group('day')): 
       errors.append('Error: %s is not a valid day!' % m.group('day')) 
      if int(m.group('month')) < 1 or 12 < int(m.group('month')): 
       errors.append('Error: %s is not a valid month!' % m.group('month')) 

    # extracts the generic words 
    regex_pattern = sub(r'###', '(?:.+?)', pattern) 
    regex_pattern = sub(r'##', '(.+?)', regex_pattern) 
    regex_pattern = sub(r'#', '(?:.+?)', regex_pattern) 
    for word in match(regex_pattern, input).groups(): 
     if not word.strip() in valid_words: 
      errors.append('Error: %s is not a valid word!' % word) 

    # extracts the numbers 
    regex_pattern = sub(r'###', '(?:.+?)', pattern) 
    regex_pattern = sub(r'##', '(?:.+?)', regex_pattern) 
    regex_pattern = sub(r'#', '(.+?)', regex_pattern) 
    for number in match(regex_pattern, input).groups(): 
     if not match(r'\d+', number): 
      errors.append('Error: %s is not a valid number!' % number) 

    if len(errors) == 0: 
     return True 
    else: 
     return '\n'.join(errors) 

print match_string('1 and 2 are numbers foo and bar are strings 12-1-2013 is a date', '# and # are numbers ## and ## are strings ### is a date', ['foo', 'bar'], '%m-%d-%y') 
print 
print match_string('1 is a number foo is a string 12-1-2013 is a date', '# is a number ## is a string ### is a date', ['foo'], '%m-%d-%y') 
print 
print match_string('foo is a number bar is a string 12-1-2013 is a date', '# is a number ## is a string ### is a date', ['foo'], '%m-%d-%y') 
print 
print match_string('1 is a number bar is a string 12-1-2013 is a date', '# is a number ## is a string ### is a date', ['foo'], '%m-%d-%y') 
print 
print match_string('1 is a number foo is a string January is a date', '# is a number ## is a string ### is a date', ['foo'], '%m-%d-%y') 

Производит результат:

True 

True 

Error: bar is not a valid word! 
Error: foo is not a valid number! 

Error: bar is not a valid word! 

Error: January is not a valid date! 

Как вы можете видеть ваш второй пример имеет две ошибки, а не один.

EDIT:

Я переделал программу без использования регулярных выражений. Это должно быть более эффективным. Может показаться, что он проходит один раз только один раз, но он по-прежнему считывает несколько символов разным способом с помощью метода startswith().

Эта версия немедленно возвращается, когда обнаружена ошибка. Следовательно, он будет обнаруживать только первую ошибку для каждого входа.

def match_string(input, pattern, valid_words, date_format): 
    print '\n> match_string(\'%s\', \'%s\', %s, \'%s\')' % (input, pattern, valid_words, date_format) 

    digits = '' 
    inputIndex = 0 
    patternIndex = 0 

    while inputIndex < len(input) and patternIndex < len(pattern): 
     if pattern[patternIndex] == '#': 
      patternIndex += 1 
      if pattern[patternIndex] == '#': 
       patternIndex += 1 
       if pattern[patternIndex] == '#': 

        # validate date 
        date_formatIndex = 0 
        while inputIndex < len(input) and date_formatIndex < len(date_format): 

         if input[inputIndex] == date_format[date_formatIndex]: 
          inputIndex += 1 
          date_formatIndex += 1 

         elif input[inputIndex] in digits: 

          startIndex = inputIndex 
          while inputIndex < len(input) and input[inputIndex] in digits: 
           inputIndex += 1 
          number = int(input[startIndex:inputIndex]) 

          if date_format[date_formatIndex:].startswith('%y'): 
           placeholder = True 
          elif date_format[date_formatIndex:].startswith('%m'): 
           if number < 1 or 12 < number: 
            return 'Error: expected a month between 1 and 12\n input %d -> "...%s"\n pattern %d -> "...%s"\n date format %d -> "...%s"' % (startIndex, input[startIndex:], patternIndex - 2, pattern[patternIndex - 2:], date_formatIndex, date_format[date_formatIndex:]) 

          elif date_format[date_formatIndex:].startswith('%d'): 
           if number < 1 or 31 < number: 
            return 'Error: expected a day between 1 and 31\n input %d -> "...%s"\n pattern %d -> "...%s"\n date format %d -> "...%s"' % (startIndex, input[startIndex:], patternIndex - 2, pattern[patternIndex - 2:], date_formatIndex, date_format[date_formatIndex:]) 

          else: 
           return 'Error: input doesn\'t match date format\n input %d -> "...%s"\n pattern %d -> "...%s"\n date format %d -> "...%s"' % (startIndex, input[startIndex:], patternIndex - 2, pattern[patternIndex - 2:], date_formatIndex, date_format[date_formatIndex:]) 

          date_formatIndex += 2 

         else: 
          return 'Error: input doesn\'t match date format\n input %d -> "...%s"\n pattern %d -> "...%s"\n date format %d -> "...%s"' % (inputIndex, input[inputIndex:], patternIndex - 2, pattern[patternIndex - 2:], date_formatIndex, date_format[date_formatIndex:]) 

        patternIndex += 1 

       else: 
        # validate word 
        valid = False 
        for word in valid_words: 
         if input[inputIndex:].startswith(word): 
          valid = True 
          inputIndex += len(word) 
          break 
        if not valid: 
         return 'Error: expected a valid word\n input %d -> "...%s"\n pattern %d -> "...%s"' % (inputIndex, input[inputIndex:], patternIndex - 2, pattern[patternIndex - 2:])      

      else: 
       # validate number 
       if not input[inputIndex] in digits: 
        return 'Error: expected a number\n input %d -> "...%s"\n pattern %d -> "...%s"' % (inputIndex, input[inputIndex:], patternIndex - 1, pattern[patternIndex - 1:]) 
       while inputIndex < len(input) and input[inputIndex] in digits: 
        inputIndex += 1 

     elif input[inputIndex] != pattern[patternIndex]: 
      return 'Error: input and pattern do not match\n input %d -> "...%s"\n pattern %d -> "...%s"' % (inputIndex, input[inputIndex:], patternIndex, pattern[patternIndex:]) 
     else: 
      inputIndex += 1    
      patternIndex += 1 
    return True 

print match_string('1 and 2 are numbers foo and bar are strings 12-1-2013 is a date', '# and # are numbers ## and ## are strings ### is a date', ['foo', 'bar'], '%m-%d-%y') 
print match_string('1 is a number foo is a string 12-1-2013 is a date', '# is a number ## is a string ### is a date', ['foo'], '%m-%d-%y') 
print match_string('foo is a number bar is a string 12-1-2013 is a date', '# is a number ## is a string ### is a date', ['foo'], '%m-%d-%y') 
print match_string('1 is a number bar is a string 12-1-2013 is a date', '# is a number ## is a string ### is a date', ['foo'], '%m-%d-%y') 
print match_string('1 is a number foo is a string January is a date', '# is a number ## is a string ### is a date', ['foo'], '%m-%d-%y') 
print match_string('1 and 2 are numbers foo and bar are strings 15-1-2013 is a date', '# and # are numbers ## and ## are strings ### is a date', ['foo', 'bar'], '%m-%d-%y') 
print match_string('1 and 2 are numbers foo and bar are strings 08-42-2013 is a date', '# and # are numbers ## and ## are strings ### is a date', ['foo', 'bar'], '%m-%d-%y') 
print match_string('1 and 2 are numbers foo and bar are strings 08;4;2013 is a date', '# and # are numbers ## and ## are strings ### is a date', ['foo', 'bar'], '%m-%d-%y') 
print match_string('1 and 2 are numbers foo and bar are strings 08-4-2013 is a date', '# and # are numbers ## and ## are strings ### is a date', ['foo', 'bar'], '~%m-%d-%y') 

Производит результат (я добавил еще несколько тестов):

> match_string('1 and 2 are numbers foo and bar are strings 12-1-2013 is a date', '# and # are numbers ## and ## are strings ### is a date', ['foo', 'bar'], '%m-%d-%y') 
True 

> match_string('1 is a number foo is a string 12-1-2013 is a date', '# is a number ## is a string ### is a date', ['foo'], '%m-%d-%y') 
True 

> match_string('foo is a number bar is a string 12-1-2013 is a date', '# is a number ## is a string ### is a date', ['foo'], '%m-%d-%y') 
Error: expected a number 
input 0 -> "...foo is a number bar is a string 12-1-2013 is a date" 
pattern 0 -> "...# is a number ## is a string ### is a date" 

> match_string('1 is a number bar is a string 12-1-2013 is a date', '# is a number ## is a string ### is a date', ['foo'], '%m-%d-%y') 
Error: expected a valid word 
input 14 -> "...bar is a string 12-1-2013 is a date" 
pattern 14 -> "...## is a string ### is a date" 

> match_string('1 is a number foo is a string January is a date', '# is a number ## is a string ### is a date', ['foo'], '%m-%d-%y') 
Error: input doesn't match date format 
input 30 -> "...January is a date" 
pattern 29 -> "...### is a date" 
date format 0 -> "...%m-%d-%y" 

> match_string('1 and 2 are numbers foo and bar are strings 15-1-2013 is a date', '# and # are numbers ## and ## are strings ### is a date', ['foo', 'bar'], '%m-%d-%y') 
Error: expected a month between 1 and 12 
input 44 -> "...15-1-2013 is a date" 
pattern 42 -> "...### is a date" 
date format 0 -> "...%m-%d-%y" 

> match_string('1 and 2 are numbers foo and bar are strings 08-42-2013 is a date', '# and # are numbers ## and ## are strings ### is a date', ['foo', 'bar'], '%m-%d-%y') 
Error: expected a day between 1 and 31 
input 47 -> "...42-2013 is a date" 
pattern 42 -> "...### is a date" 
date format 3 -> "...%d-%y" 

> match_string('1 and 2 are numbers foo and bar are strings 08;4;2013 is a date', '# and # are numbers ## and ## are strings ### is a date', ['foo', 'bar'], '%m-%d-%y') 
Error: input doesn't match date format 
input 46 -> "...;4;2013 is a date" 
pattern 42 -> "...### is a date" 
date format 2 -> "...-%d-%y" 

> match_string('1 and 2 are numbers foo and bar are strings 08-4-2013 is a date', '# and # are numbers ## and ## are strings ### is a date', ['foo', 'bar'], '~%m-%d-%y') 
Error: input doesn't match date format 
input 44 -> "...08-4-2013 is a date" 
pattern 42 -> "...### is a date" 
date format 0 -> "...~%m-%d-%y" 
+0

Отличные @Akinakes! Я применил вариацию этого. Незначительный вопрос. является sub(), необходимым для замены '#' с помощью регулярных выражений? Можем ли мы просто использовать replace()? – Neil

+0

Строковая манипуляция предпочтительнее регулярного выражения, если это возможно, поэтому в этом случае вместо replace() лучше, чем sub(). Я не думал об этом, когда писал код. Обратите внимание, что еще быстрее реализовать собственный метод замены, который напрямую манипулирует строкой и, следовательно, должен проходить только один раз, а не несколько раз, как в моем коде. – Akinakes

+1

@Neil Я переделал программу без регулярного выражения для сравнения. Более поздняя версия должна быть быстрее. – Akinakes

Смежные вопросы