Регулярное выражение в Python: отделяйте слова от чисел ТОЛЬКО когда их нет в списке - PullRequest
2 голосов
/ 15 января 2020

У меня есть список, содержащий некоторые замены, которые мне нужно сохранить. Например, список замещения: ['1st','2nd','10th','100th','1st nation','xlr8','5pin','h20'].

Как правило, строки, содержащие буквы алфавита c, должны разбивать цифры и буквы следующим образом:

text= re.sub(r'(?<=\d)(?=[^\d\s])|(?<=[^\d\s])(?=\d)',' ',text,0,re.IGNORECASE)

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

Original       Regex
ABC10 DEF  --> ABC 10 DEF
ABC DEF10  --> ABC DEF 10
ABC 10DEF  --> ABC 10 DEF
10ABC DEF  --> 10 ABC DEF

Тем не менее, есть некоторые alphanumeri c слова, которые являются частью списка замещения, которые не могут быть разделены. Например, следующая строка, содержащая 1ST, которая является частью списка подстановки, не должна быть разделена, и их следует опускать вместо добавления пробела:

Original            Regex                Expected
1ST DEF 100CD  -->  1 ST DEF 100 CD  --> 1ST DEF 100 CD
ABC 1ST 100CD  -->  ABC 1 ST 100 CD  --> ABC 1ST 100 CD
100TH DEF 100CD ->  100 TH DEF 100 CD -> 100TH DEF 100 CD
10TH DEF 100CD  ->  10 TH DEF 100 CD  -> 10TH DEF 100 CD 

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

(?(?=condition)(then1|then2|then3)|(else1|else2|else3))

Исходя из синтаксиса, у меня должно быть что-то вроде следующего:

?(?!1ST)((?<=\d)(?=[^\d\s])|(?<=[^\d\s])(?=\d)))

где (?! ...) будет включать возможные замены, которые следует избегать при сопоставлении с шаблоном регулярных выражений, в этом случае слова 1ST 10TH 100TH

Как можно избежать сопоставления подстановок слов в строка?

Спасибо за преподавание регулярных выражений:)

Ответы [ 3 ]

2 голосов
/ 15 января 2020

Другой способ использования regex, (*SKIP)(*FAIL) и f-strings:

import regex as re

lst = ['1st','2nd','1st nation','xlr8','5pin','h20']

data = """
ABC10 DEF
ABC DEF10
ABC 10DEF
10ABC DEF
1ST DEF 100CD
ABC 1ST 100CD"""

rx = re.compile(
    rf"""
    (?:{"|".join(item.upper() for item in lst)})(*SKIP)(*FAIL)
    |
    (?<=\d)(?=[^\d\s])|(?<=[^\d\s])(?=\d)
    """, re.X)

data = rx.sub(' ', data)
print(data)

Это дает

ABC 10 DEF
ABC DEF 10
ABC 10 DEF
10 ABC DEF
1ST DEF 100 CD
ABC 1ST 100 CD
2 голосов
/ 15 января 2020

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

import re

subs = ['1st','2nd','1st nation','xlr8','5pin','h20']
text = """
ABC10 DEF
1ST DEF 100CD
ABC 1ST 100CD
AN XLR8 45X
NO H20 DEF
A4B PLUS
"""

def add_spaces(m):
    if m.group().lower() in subs:
        return m.group()
    res = m.group(1)
    if len(res):
        res += ' '
    res += m.group(2)
    if len(m.group(3)):
        res += ' '
    res += m.group(3)
    return res

text = re.sub(r'\b([^\d\s]*)(\d+)([^\d\s]*)\b', lambda m: add_spaces(m), text)
print(text)

Вывод:

ABC 10 DEF
1ST DEF 100 CD
ABC 1ST 100 CD
AN XLR8 45 X
NO H20 DEF
A 4 B PLUS

Вы можете упростить лямбда-функцию до

def add_spaces(m):
    if m.group().lower() in subs:
        return m.group()
    return m.group(1) + ' ' + m.group(2) + ' ' + m.group(3)

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

text = re.sub(r' +', ' ', text)
1 голос
/ 16 января 2020

Когда вы имеете дело с исключениями, самый простой и безопасный способ - использовать подход " лучший трюк за все время ". При замене этот трюк означает: сохранить то, что захвачено, удалить то, что соответствует или наоборот. В терминах регулярных выражений вы должны использовать чередование и группу захвата вокруг одного (или некоторых в сложном сценарии ios) из них, чтобы иметь возможность анализировать структуру совпадений после совпадение найдено.

Итак, сначала , используйте список исключений для построения первой части чередования:

exception_rx = "|".join(map(re.escape, exceptions))

Примечание re.escape добавляет обратные слеши, где необходимо поддерживать любые специальные символы в исключениях. Если ваши исключения все alphanumeri c, вам это не нужно, и вы можете просто использовать exception_rx = "|".join(exceptions). Или даже exception_rx = rf'\b(?:{"|".join(exceptions)})\b', чтобы сопоставлять их только как целые слова.

Далее , вам нужен шаблон, который найдет все совпадения независимо от контекста, тот, который я уже опубликовал :

generic_rx = r'(?<=\d)(?=[^\d\s])|(?<=[^\d\s])(?=\d)'

Наконец , соедините их по схеме (exceptions_rx)|generic_rx:

rx = re.compile(rf'({exception_rx})|{generic_rx}', re.I)   

и замените с помощью .sub():

s = rx.sub(lambda x: x.group(1) or " ", s)

Здесь lambda x: x.group(1) or " " означает возвращать значение группы 1, если группа 1 соответствует, в противном случае заменить пробелом .

См. Демонстрационную версию Python :

import re

exceptions = ['1st','2nd','10th','100th','1st nation','xlr8','5pin','h20', '12th'] # '12th' added
exception_rx = '|'.join(map(re.escape, exceptions))
generic_rx = r'(?<=\d)(?=[^\d\s])|(?<=[^\d\s])(?=\d)'
rx = re.compile(rf'({exception_rx})|{generic_rx}', re.I)

string_lst = ['1ST DEF 100CD','ABC 1ST 100CD','WEST 12TH APARTMENT']
for s in string_lst:
    print(rx.sub(lambda x: x.group(1) or " ", s))

Выход:

1ST DEF 100 CD
ABC 1ST 100 CD
WEST 12TH APARTMENT
...