Убедитесь, что строка состоит только из диапазона чисел Python 2.7 - PullRequest
0 голосов
/ 27 июня 2018

Мне нужно проверить, является ли строка правильно отформатированным диапазоном чисел в Python2.7, я имею в виду диапазон, который мы используем, например, при выборе некоторых конкретных страниц для печати, например:

1,3,5-7, 10, 15 - 20

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

Я попробовал регулярное выражение без особой удачи. В Python 3 есть re.fullmatch, который, по-видимому, совпадает только в том случае, если вся строка соответствует шаблону, в Python 2.7 не существует, однако я попытался таким образом сделать это в Python 2, и, по-видимому, работает правильно, но моё регулярное выражение кажется неправильным. Я пробовал много разных регулярных выражений, и все они терпели неудачу тем или иным способом, последний допустил неправильные символы в начале строки (это только для запятых, еще не дошел до черточек):

^\d+$|(\d+)(\s?)(,{1})(\s?)(\d+)

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

Ответы [ 5 ]

0 голосов
/ 27 июня 2018

Я бы просто разделил запятую, затем опционально на дефис и просто контролировал, чтобы каждый разделенный дефисом диапазон содержал не более целых чисел. Следующая функция возвращает генератор, который возвращает список из 1 или 2 целых чисел на диапазон после проверки синтаксиса:

def parse(string):
    ranges = re.split(r'\s*,\s*', string.strip())  # split on comma ignoring spaces
    for i in ranges:
        limits = re.split(r'\s*-\s*', i)   # optionaly splits on hyphen
        if len(limits) > 2:                # one max
            raise ValueError('More than one hyphen in a range ({})'
                     .format(i))
        if any([ re.match('[0-9]+', num) == None for num in limits ]): # only integers allowed
            raise ValueError('A page number is not an integer ({})'
                     .format(num))
        yield [ int(num) for num in limits ]

Это с радостью игнорирует любые пробелы или табуляции, все еще гарантируя правильный синтаксис

0 голосов
/ 27 июня 2018

Вы можете использовать строку поиска '^(?:\s+|\d+\s*-\s*\d+|,|\d+)*$', которая будет искать либо пробелы, либо диапазон номеров (с любым количеством пробелов, например: 1 - 3 или 1-3 оба совпадения), запятые или числа.

Проблема в том, что не требуется запятая для разделения каждого вхождения, поэтому 1 2 3 4 является действительным, и не имеет значения, повторяются ли запятые, поэтому ,, , , также допустимо. Если вы заботитесь об этих вещах, то это не сработает для вас.

0 голосов
/ 27 июня 2018

Так как вам все равно нужно разобрать его, проще использовать синтаксический анализ в качестве проверки. Многие библиотеки Python также используют этот подход, например, JSON. Это позволяет избежать дублирования логики (1. проверка, 2. разбор), допускает более выразительные сообщения об ошибках и часто намного проще.

Чтобы проанализировать один литерал, такой как 4 или 1 - 3, разделите его и преобразуйте значения начала / остановки. Это автоматически вызывает ValueError, если числа не являются действительными целыми числами.

def page_range(literal):
    """
    Convert a single page literal into a sequence of pages

    :raises ValueError: if literal does not denote a valid page range
    """
    start, sep, stop = literal.partition('-') # '1 -3' => '1 ', '-', '3'; ' 4' => ' 4', '', ''
    if not start:  # may want to raise an error for empty page literals
        return []
    if not sep:  # no '-' in literal, just the start
        return [int(start)]
    # sep is present, literal is a range of pages
    return list(range(int(start), int(stop) + 1))

Вы можете использовать это для объединения страниц из нескольких литералов, таких как 4, 1-3. Используя исключения, вы можете вызвать ошибку для части недопустимого литерала:

def pages(literals):
    for literal in literals.split(','):
        try:
            yield page_range(literal.strip())
        except ValueError:  # parsing failed, raise manually to add details
            raise ValueError('Invalid page range: %r' % literal)
0 голосов
/ 27 июня 2018

Если вы используете регулярное выражение, возможно, это будет соответствовать вашим требованиям:

^\d+(?: *- *\d+)?(?:, *\d+(?: *- *\d+)?)*$

Объяснение

  • ^ Утверждение начала строки
  • \d+ Совпадение с одной или несколькими цифрами
  • (?: Группа без захвата
    • *- *\d+ Соответствует нулю или нескольким пробелам, за которыми следуют дефис, один или несколько пробелов и одна или несколько цифр
  • )? Закрыть группу без захвата и сделать ее необязательной
  • (?: Группа без захвата
    • , *\d+ Соответствует запятой, нулю или нескольким пробелам и одной или нескольким цифрам
    • (?: Группа без захвата
      • *- *\d+ Соответствует нулю или нескольким пробелам, за которыми следуют дефис, ноль или несколько пробелов и одна или несколько цифр
    • )? Закрыть группу без захвата и сделать ее необязательной
  • )* Закрыть группу без захвата и повторить ее ноль или более раз
  • $ Утверждение конца строки

Демо

0 голосов
/ 27 июня 2018

Я бы не стал беспокоиться о регулярных выражениях.

def verify(s):
    last = 0
    for r in s.replace(' ', '').split(','):
        # A single integer...
        try:
            last = int(r)
            continue
        except ValueError:
            pass

        # ... or a hyphen-separated pair of increasing integers
        try:
            x, y = r.split("-")
            if not (last < x <= y):
                return False
            last = y
        except ValueError:
            return False

    return True

Это также гарантирует, что значения располагаются в отсортированном порядке, что регулярное выражение не в состоянии сделать.

...