R L'expression régulière pour extraire le code postal du Royaume-uni À partir d'une adresse N'est pas commandée

j'essaie d'extraire les codes postaux du Royaume-Uni des chaînes d'adresses en R, en utilisant l'expression régulière fournie par le gouvernement du Royaume-Uni ici .

voici ma fonction:

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))
}

selon le document d'orientation, la logique recherchée par cette expression régulière est la suivante:

"GIR 0AA" OU Une lettre suivie d'un ou de deux nombres OU d'Une lettre suivie d'une seconde lettre qui doit être l'un des ABCDEFGHJ KLMNOPQRSTUVWXY (i.e..pas I), puis suivi par un ou deux chiffres ou une lettre suivie d'un chiffre puis d'un autre lettre OU UN code postal où la première partie doit être Une lettre suivi d'une deuxième lettre qui doit être celle D'ABCDEFGH JKLMNOPQRSTUVWXY (i.e..pas moi) et ensuite suivi par un nombre et une autre lettre après celle-ci et la deuxième partie (séparée par un espace de la première partie) doit être Un nombre suivi par deux lettre. Une combinaison de caractères majuscules et minuscules est autorisée. Remarque: la durée est déterminée par l'expression régulière et est entre 2 et 8 caractères.

mon problème est que cette logique n'est pas complètement préservée lors de l'utilisation de l'expression régulière sans les ancrages ^ et $ (comme je dois le faire dans ce scénario parce que le code postal pourrait être n'importe où dans les chaînes d'adresses); ce que je suis aux prises avec c'est comment préserver l'ordre et le nombre de caractères pour chaque segment dans une correspondance partielle (par opposition à complète).

prenons l'exemple suivant:

> address_to_postcode("1A noplace road, random city, NR1 2PK, UK")
[1] "NR1 2PK"

selon la logique de la ligne directrice, la deuxième lettre dans le code postal ne peut pas être "z" (et il y a d'autres exclusions aussi); cependant, regardez ce qui se passe quand j'ajoute un "z":

> address_to_postcode("1A noplace road, random city, NZ1 2PK, UK")
[1] "Z1 2PK"

... alors que dans ce cas j'attendrais la sortie de être NA .

ajouter les ancres (pour un cas d'usage différent) ne semble pas aider car le 'z' est toujours accepté bien qu'il soit au mauvais endroit:

> 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

deux questions:

  1. ai-je mal compris la logique de l'expression régulière et
  2. si non, Comment puis-je le corriger (i.e. pourquoi ne sont pas la lettre spécifiée et des gammes de caractères exclusifs à leur position dans l'expression régulière)?
3
demandé sur Amy M 2018-08-13 21:45:06

1 réponses

Modifier

depuis la publication de cette réponse, j'ai creusé plus profondément dans le regex du gouvernement britannique et j'ai trouvé encore plus de problèmes. j'ai posté une autre réponse ici qui décrit toutes les questions et fournit des solutions de rechange à leur mal formaté regex.


Note

veuillez noter que je poste le RAW regex ici. Vous aurez besoin d'échapper à certains caractères (comme backslashs) \ ) lors du portage sur .


Questions

vous avez beaucoup de problèmes ici, qui sont tous causés par celui qui a créé le document que vous récupérez votre regex ou le codeur qui l'a créé.

1. Le caractère d'espace

mon avis est que lorsque vous avez copié l'expression régulière à partir du lien que vous avez fourni il converti le caractère d'espace dans un caractère newline et vous l'avez enlevé (c'est exactement ce que j'ai fait au début). Vous devez, à la place, le changer en un caractère d'espace.

^([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. Limites

vous devez enlever les ancrages ^ et $ car ceux-ci indiquent le début et la fin de la ligne. À la place, enveloppez votre regex dans (?:) et placez un \b (limite des mots) à chaque extrémité comme suit. En fait, le regex dans la documentation est incorrect (voir note latérale pour plus d'information) car il ne pourra pas ancrer le motif correctement.

voir regex utilisé ici

\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. Contrôle de classe de caractère

il y a un manquant - dans la classe des caractères comme indiqué par @deadcrab dans sa réponse ici .

\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. Ils ont rendu la mauvaise classe de caractère optionnelle!

Dans la documentation clairement : le

code postal en deux parties où la première partie doit être:

  • une lettre suivie d'une deuxième lettre qui doit être une de ABCDEFGHJKLMNOPQRSTUVWXY (i.e..pas I ) et suivi d'un numéro et éventuellement une autre lettre après que

ils ont rendu la mauvaise classe de caractère optionnelle!

\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. Le tout est juste horrible...

il y a tellement de choses qui ne vont pas avec ce regex que j'ai décidé de le réécrire. Il peut très facilement être simplifié pour effectuer une fraction des étapes qu'il faut actuellement pour comparer le texte.

\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

réponse

comme mentionné dans les commentaires ci-dessous ma réponse, certains codes postaux manquent le caractère d'espace. Pour les espaces manquants dans les codes postaux (par exemple NR12PK ), il suffit d'ajouter ? après les espaces comme indiqué dans le regex ci-dessous:

\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
                                             ^^                             ^^

vous pouvez également raccourcir le regex ci-dessus avec l'indicateur suivant et utiliser le drapeau cas-insensible ( ignore.case(pattern) ou ignore_case = TRUE dans , selon la méthode utilisée.):

\b(?:[A-Z][A-HJ-Y]?[0-9][0-9A-Z]? ?[0-9][A-Z]{2}|GIR ?0A{2})\b

Note

veuillez noter que les expressions régulières ne valident que le(S) format (s) possible (s) d'une chaîne de caractères et ne peuvent pas réellement identifier si un code postal existe ou non légitimement. Pour cela, vous devez utiliser une API. Il y a aussi des cas extrêmes où ce regex ne correspondra pas correctement aux postcodes valides. Pour une liste de ces codes postaux, s'il vous plaît voir ce article de Wikipedia .

La regex

  • Britannique Territoires D'Outre-Mer
  • Les Forces Britanniques Bureau De Poste
    • bien qu'ils l'aient récemment changé pour s'aligner avec le système de code postal britannique à BF , suivi d'un nombre (à partir de BF1 ), ils sont considérés comme optional alternative postcodes
  • cas spéciaux décrits dans cet article (ainsi que SAN TA1 - un code postal valide pour le Père Noël!)

voir ce regex utilisé ici .

\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

je recommande également à quiconque mettant en œuvre cette réponse de lire cette question StackOverflow intitulée UK Postcode Regex (Comprehensive) .


note

La documentation liée ( en Vrac de Transfert de Données: Validation Supplémentaire pour les autorités de certification Télécharger - Section 3. UK Postcode Regular Expression ) a en fait une expression régulière mal écrite.

tel que mentionné dans la section Issues , ils devraient avoir:

  1. a enveloppé l'expression entière dans (?:) et placé les ancres autour du groupe de non-capture. Leur expression régulière, telle qu'elle se présente, échouera dans certains cas comme vu ici .
  2. l'expression régulière est également manquante - dans l'une des classes de caractères
  3. il a également fait la mauvaise classe de caractère facultatif.
6
répondu ctwheels 2018-10-04 14:20:39