4

ここで英国政府が提供する正規表現を使用して、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" または 1 文字の後に 1 つまたは 2 つの数字が続く、または 1 つの文字の後に ABCDEFGHJ KLMNOPQRSTUVWXY (つまり、I ではない) のいずれかでなければならない 2 番目の文字が続き、その後に 1 つまたは 2 つの数字が続く、または 1 つの文字の後に 1 つが続く数字の後に別の文字または 2 部構成の郵便番号。最初の部分は 1 文字で、2 番目の文字は ABCDEFGH JKLMNOPQRSTUVWXY (つまり、I ではない) のいずれかで、その後に 1 つの数字とオプションでさらに文字が続きます。 AND 2 番目の部分 (最初の部分からスペースで区切られている) は、1 つの数字の後に 2 つの文字が続く必要があります。大文字と小文字の組み合わせが許可されています。注: 長さは正規表現によって決定され、2 ~ 8 文字です。

^私の問題は、アンカーとなしで正規表現を使用すると、このロジックが完全に保持されないこと$です (郵便番号が住所文字列内のどこにでもある可能性があるため、このシナリオで行う必要があります)。私が苦労しているのは、部分的な (完全ではなく) 文字列一致で各セグメントの順序と文字数を保持する方法です。

次の例を検討してください。

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

ガイドラインの論理によれば、郵便番号の 2 番目の文字を「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. 正規表現の論理を誤解していて、
  2. そうでない場合、どうすれば修正できますか (つまり、指定された文字と文字の範囲が正規表現内の位置に限定されないのはなぜですか)?
4

2 に答える 2

14

編集

この回答を投稿してから、英国政府の正規表現をさらに深く掘り下げ、さらに多くの問題を発見しました。すべての問題を説明し、フォーマットが不十分な正規表現の代替手段を提供する別の回答をここに投稿しました。


ノート

生の正規表現をここに投稿していることに注意してください。\に移植するときは、特定の文字 (バックスラッシュなど) をエスケープする必要があります。


問題

ここには多くの問題があります。これらはすべて、正規表現を取得するドキュメントを作成した人、またはそれを作成したコーダーによって引き起こされます。

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の回答here-で指摘されているように、文字クラスに欠落があります。

\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. 彼らは間違った文字クラスをオプションにしました!

ドキュメントには、次のように明確に記載されています。

2 部構成の郵便番号で、最初の部分は次のとおりです。

  • ABCDEFGHJKLMNOPQRSTUVWXY1 つの文字の後に(ie.not )の 1 つである必要がある 2 番目の文字がI続き、その後に 1 つの数字とオプションでさらに文字が続きます。

彼らは間違った文字クラスをオプションにしました!

\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

また、この回答を実装している人には、この StackOverflow の質問である UK Postcode Regex (Comprehensive)を読むことをお勧めします。


サイドノート

リンク先のドキュメント (バルク データ転送: CAS アップロードの追加検証 - セクション 3. 英国の郵便番号の正規表現) には、実際には不適切に記述された正規表現があります。

問題セクションで説明したように、次のものが必要です。

  1. 式全体をラップして(?:)、非キャプチャ グループの周囲にアンカーを配置しました。そのままの正規表現は、ここで見られるように、いくつかのケースでは失敗します。
  2. -文字クラスの 1 つに も正規表現がありません
  3. また、間違った文字クラスをオプションにしました。
于 2018-08-13T18:56:41.733 に答える