Регулярное выражение для сопоставления различных типов нумерованных списков

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

Я определил «списки» как набор канонических англо-саксонских соглашений, т.е.

Числа

1 2 3
1. 2. 3.
1) 2) 3)
(1) (2) (3)
1.1 1.2 1.2.1
1.1. 1.2. 1.3.
1.1) 1.2) 1.3)
(1.1) (1.2) (1.3)

Буквы

a b c
a. b. c.
a) b) c)
(a) (b) (c) 
A B C
A. B. C. 
A) B) C)
(A) (B) (C)

римские цифры

i ii iii
i. ii. iii.
i) ii) iii)
(i) (ii) (iii)
I II III
i. ii. iii.
i) ii) iii)
(i) (ii) (iii)

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

Вот регулярное выражение, которое я создал для решения этой проблемы (в Python):

numex = r'(?:\d{1,3}'\   # 1, 2, 3
    '(?:\.\d{1,3}){0,4}'\ # 1.1, 1.1.1.1
    '|[A-Z]{1,2}'\        # A. B. C.
    '|[ivxcl]{1,6}'       # i, iii, ...

rex = re.compile(r'(\(?%s\)|%s\.?)' % numex, re.I) # re.U?

rex.match("123. Some paragraph")    

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

Между прочим, для моего конкретного случая использования я бы не ожидал, что число списков будет больше 25-50.

Спасибо за чтение.

Брайан


person Brian M. Hunt    schedule 27.06.2010    source источник


Ответы (2)


Вот Wikified решение:

 numex = r"""^(?:
      \d{1,3}                 # 1, 2, 3
          (?:\.\d{1,3}){0,4}  # 1.1, 1.1.1.1
    | [B-H] | [J-Z]         # A, B - Z caps at 26.
    | [AI](?!\s)            # Note: "A" and "I" can properly start non-lists
    | [a-z]                 # a - z
    | [ivxcl]{1,6}          # Roman ii, etc
    | [IVXCL]{1,6}          # Roman IV, etc.
    )
    """

 rex = re.compile(r'^\s*(\(?%s\)|%s\.?)\s+(.*)'
   % (numex, numex), re.X)

Дополнения, изменения и предложения приветствуются.

person Brian M. Hunt    schedule 27.06.2010

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

rex = re.compile(r'(\(?\b%s\)|\b%s\b\.?)' % (numex, numes), re.I|re.M)

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

Возможно, вы захотите закрепить поиск в начале строки; ведь эти символы должны быть первыми в строке (кроме, может быть, пробелов). Отрицательный просмотр назад не будет использоваться в Python, потому что Python не поддерживает просмотр назад переменной длины, поэтому вы можете добавить это вне соответствующих круглых скобок:

rex = re.compile(r'^\s*(\(?%s\)|%s\b\.?)' % (numex, numex), re.I|re.M)

Конечно, теперь вы должны посмотреть на group(1) объекта соответствия, чтобы получить только фактическое совпадение, а не начальный пробел.

Вы по-прежнему будете совпадать слишком часто (например, предложения, начинающиеся с I thought so или It was a dark and stormy night, но ваши правила позволяют это, и я думаю, вы знаете об этом.

person Tim Pietzcker    schedule 27.06.2010
comment
Спасибо за сообщение. Я только что заметил эту проблему с альфа-ложными срабатываниями. Я думаю, что решил это путем (1) упрощения путем сокращения списков символов до одной буквы и (2) с отрицательным просмотром нулевой ширины. - person Brian M. Hunt; 27.06.2010