r Регулярное выражение для извлечения почтового индекса из Великобритании не упорядочено
Я пытаюсь извлечь почтовые индексы Великобритании из строк адреса в R, используя регулярное выражение, предоставленное здесь правительством Великобритании.
Вот моя функция:
address_to_postcode <- function(addresses) {
# 1. Convert addresses to upper case
addresses = toupper(addresses)
# 2. Regular expression for UK postcodes:
pcd_regex = "[Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([A-Za-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z])))) {0,1}[0-9][A-Za-z]{2})"
# 3. Check if a postcode is present in each address or not (return TRUE if present, else FALSE)
present <- grepl(pcd_regex, addresses)
# 4. Extract postcodes matching the regular expression for a valid UK postcode
postcodes <- regmatches(addresses, regexpr(pcd_regex, addresses))
# 5. Return NA where an address does not contain a (valid format) UK postcode
postcodes_out <- list()
postcodes_out[present] <- postcodes
postcodes_out[!present] <- NA
# 6. Return the results in a vector (should be same length as input vector)
return(do.call(c, postcodes_out))
}
Согласно руководству, логика, которую ищет это регулярное выражение, следующая:
"GIR 0AA" ИЛИ Одна буква, за которой следуют одно или два числа ИЛИ Одна буква, за которой следует вторая буква, которая должна быть одной из ABCDEFGHJ KLMNOPQRSTUVWXY (т. Е. Не I), после чего следуют одно или два числа ИЛИ Одна буква, за которой следует одна число, а затем еще одна буква ИЛИ Двухчастный почтовый индекс, где первая часть должна быть одна буква, за которой следует вторая буква, которая должна быть одной из ABCDEFGH JKLMNOPQRSTUVWXY (т.е. не I), а затем следует одна цифра и, возможно, еще одна буква после этого И вторая часть (отделенная пробелом от первой части) должна быть одна цифра, за которой следуют две буквы. Допускается сочетание символов верхнего и нижнего регистра. Примечание: длина определяется регулярным выражением и составляет от 2 до 8 символов.
Моя проблема в том, что эта логика не полностью сохраняется при использовании регулярного выражения без ^
а также $
якоря (как я должен сделать в этом сценарии, потому что почтовый индекс может быть где угодно в адресных строках); Я борюсь с тем, как сохранить порядок и количество символов для каждого сегмента в частичном (в отличие от полного) совпадении строк.
Рассмотрим следующий пример:
> address_to_postcode("1A noplace road, random city, NR1 2PK, UK")
[1] "NR1 2PK"
Согласно логике в руководстве, вторая буква в почтовом индексе не может быть 'z' (и есть также некоторые другие исключения); однако посмотрите, что происходит, когда я добавляю "z":
> address_to_postcode("1A noplace road, random city, NZ1 2PK, UK")
[1] "Z1 2PK"
... тогда как в этом случае я ожидаю, что результат будет NA
,
Добавление якорей (для другого случая использования), кажется, не помогает, так как 'z' все еще принимается, даже если оно находится в неправильном месте:
> grepl("^[Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([A-Za-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z])))) {0,1}[0-9][A-Za-z]{2})$", "NZ1 2PK")
[1] TRUE
Два вопроса:
- Я неправильно понял логику регулярного выражения и
- Если нет, как я могу это исправить (т.е. почему указанные диапазоны букв и символов не являются исключительными для их позиции в регулярном выражении)?
2 ответа
редактировать
После публикации этого ответа я углубился в регулярное выражение правительства Великобритании и обнаружил еще больше проблем. Я разместил здесь другой ответ, который описывает все проблемы и предоставляет альтернативы их плохо отформатированному регулярному выражению.
Заметка
Обратите внимание, что я публикую здесь сырое регулярное выражение. Вам нужно будет экранировать определенные символы (например, обратные косые черты) \
) при портировании на р.
вопросы
У вас здесь много проблем, и все они вызваны тем, кто создал документ, из которого вы извлекаете регулярное выражение, или программистом, который его создал.
1. Космический персонаж
Я предполагаю, что когда вы скопировали регулярное выражение из предоставленной вами ссылки, он преобразовал символ пробела в символ новой строки, и вы удалили его (это именно то, что я сделал сначала). Вместо этого вам нужно изменить его на пробел.
^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([AZa-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z])))) [0-9][A-Za-z]{2})$
here ^
2. Границы
Вы должны удалить якоря ^
а также $
так как они указывают начало и конец строки. Вместо этого, оберните свое регулярное выражение в (?:)
и разместить \b
(граница слова) на любом конце, как показано ниже. Фактически, регулярное выражение в документации неверно (см. Примечание к статье), так как оно не сможет правильно привязать шаблон.
Смотрите регулярное выражение в использовании здесь
\b(?:([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([AZa-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z])))) [0-9][A-Za-z]{2}))\b
^^^^^ ^^^
3. Надзор за классами персонажей
Там отсутствует -
в классе персонажей, как указал deadcrab в своем ответе здесь.
\b(?:([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([A-Za-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z])))) [0-9][A-Za-z]{2}))\b
^
4. Они сделали неправильный класс символов необязательным!
В документации четко указано:
Почтовый индекс из двух частей, где первая часть должна быть:
- Одна буква, за которой следует вторая буква, которая должна быть одной из
ABCDEFGHJKLMNOPQRSTUVWXY
(ie.notI
) и затем следует одно число и, возможно, еще одна буква после этого
Они сделали неправильный класс символов необязательным!
\b(?:([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([A-Za-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z])))) [0-9][A-Za-z]{2}))\b
^^^^^^
it should be this one ^^^^^^^^
5. Все это просто ужасно...
В этом регулярном выражении так много неправильных вещей, что я просто решил переписать его. Это очень легко упростить, чтобы выполнить часть шагов, которые в настоящее время требуется для соответствия тексту.
\b(?:[A-Za-z][A-HJ-Ya-hj-y]?[0-9][0-9A-Za-z]? [0-9][A-Za-z]{2}|[Gg][Ii][Rr] 0[Aa]{2})\b
Ответ
Как упомянуто в комментариях ниже моего ответа, в некоторых почтовых индексах отсутствует пробел. Для пропущенных пробелов в почтовых индексах (например, NR12PK
), просто добавьте ?
после пробелов, как показано в регулярном выражении ниже:
\b(?:[A-Za-z][A-HJ-Ya-hj-y]?[0-9][0-9A-Za-z]? ?[0-9][A-Za-z]{2}|[Gg][Ii][Rr] ?0[Aa]{2})\b
^^ ^^
Вы также можете сократить приведенное выше регулярное выражение следующим образом и использовать флаг без учета регистра (ignore.case(pattern)
или же ignore_case = TRUE
в т, в зависимости от используемого метода.):
\b(?:[A-Z][A-HJ-Y]?[0-9][0-9A-Z]? ?[0-9][A-Z]{2}|GIR ?0A{2})\b
Заметка
Обратите внимание, что регулярные выражения только проверяют возможный формат (ы) строки и не могут фактически определить, существует ли законный почтовый индекс. Для этого вы должны использовать API. Есть также некоторые крайние случаи, когда это регулярное выражение не будет правильно соответствовать действительным почтовым индексам. Список этих почтовых индексов см. В этой статье Википедии.
Приведенное ниже регулярное выражение дополнительно соответствует следующему (сделайте его нечувствительным к регистру, чтобы совпадать и с вариантами в нижнем регистре):
- Британские заморские территории
- Почтовое отделение британских войск
- Хотя они недавно изменили его, чтобы привести в соответствие с британской системой почтовых индексов
BF
с последующим номером (начиная сBF1
), они считаются необязательными альтернативными почтовыми индексами
- Хотя они недавно изменили его, чтобы привести в соответствие с британской системой почтовых индексов
- Особые случаи, изложенные в этой статье (а также
SAN TA1
- действительный почтовый индекс для Санты!)
Смотрите это регулярное выражение в использовании здесь.
\b(?:(?:[A-Z][A-HJ-Y]?[0-9][0-9A-Z]?|ASCN|STHL|TDCU|BBND|[BFS]IQ{2}|GX11|PCRN|TKCA) ?[0-9][A-Z]{2}|GIR ?0A{2}|SAN ?TA1|AI-?[0-9]{4}|BFPO[ -]?[0-9]{2,3}|MSR[ -]?1(?:1[12]|[23][135])0|VG[ -]?11[1-6]0|[A-Z]{2} ? [0-9]{2}|KY[1-3][ -]?[0-2][0-9]{3})\b
Я бы также порекомендовал любому, кто реализует этот ответ, прочитать этот вопрос Stackru под названием "Почтовый индекс Великобритании: регулярное выражение".
Примечание
Документация, на которую вы ссылались ( Массовая передача данных: дополнительная проверка для загрузки CAS - Раздел 3. Регулярное выражение почтового индекса Великобритании) на самом деле имеет неправильно написанное регулярное выражение.
Как указано в разделе " Проблемы ", они должны иметь:
- Завернуто все выражение в
(?:)
и поместил якоря вокруг группы без захвата. Их регулярное выражение, в его нынешнем виде, в некоторых случаях потерпит неудачу, как показано здесь. - Регулярное выражение также отсутствует
-
в одном из классов персонажей - Это также сделало неправильный класс символов необязательным.
вот мое регулярное выражение
txt="0288, Bishopsgate, London Borough of Tower Hamlets, London, Greater London, England, EC2M 4QP, United Kingdom"
matches=re.findall(r'[A-Z]{1,2}[0-9][A-Z0-9]? [0-9][ABD-HJLNP-UW-Z]{2}', txt)