住所から「都道府県/市区町村/それ以降」の3ヵ所をキャプチャする正規表現を作りたい。
rex = /ごにょごにょ/
p "東京都文京区後楽1丁目3−61".match(rex).captures
#=> ["東京都", "文京区", "後楽1丁目3−61"]
みたいなやつ。なるべく短く。
実用性?
そんなもの、うちにはないよ。
TL;DR
「読むのめんどくさい」という人用に最初に最終結果を置いておきます
(...??[都道府県])((?:旭川|伊達|石狩|盛岡|奥州|田村|南相馬|那須塩原|東村山|武蔵村山|羽村|十日町|上越|富山|野々市|大町|蒲郡|四日市|姫路|大和郡山|廿日市|下松|岩国|田川|大村)市|.+?郡(?:玉村|大町|.+?)[町村]|.+?市.+?区|.+?[市区町村])(.+)
あまり厳密ではないのでちゃんとしたとこでは使わないほうがいいです
住所データを用意する
郵便局からデータをダウンロードしておく。一ヶ月毎に更新されている。
→ 郵便番号データダウンロード → ( 全国一括(zip) )
住所を正しく分割できるか確認するスクリプトを書いた。Ruby。
→ 住所を分ける正規表現をチェックするやつ - Gist
CSVの読み込みが重いから、読み込んだ後は無限ループにして正規表現を一回ずつ試せるようにした。
正規表現を考える
0.00001秒で思いついたやつ。
(.+?[都道府県])(.+?[市区町村])(.+)
「都道府県」「市区町村」という区切り文字の最短マッチで判定。
さっきのスクリプトに投げて失敗数を確認する。
失敗数:
19989/123909 (16%)
わりといける。
「京都」府
上の簡単な正規表現は、区切り文字が名前自体に含まれるときに失敗する。
なので京都府から「京都」という都が生まれる。
文字数を3〜4文字に縛って(...?[都道府県])
としてみる。
残念、今度は「東京都府中市」等で「東京都府」が爆誕した。
最短マッチにする。(.{2,3}?[都道府県])
でもOK。
(...??[都道府県])(.+?[市区町村])(.+)
失敗数:
18220/123909 (14%)
都道府県がちゃんと判定されるようになったので、ちょっと失敗率が下がった。
「○○市△△区」を区まで含むか、市のみとするか
市と区が並んでいるとき「市区町村」に区まで含む場合と、市のみとする場合がある。
前者は「政令指定都市の行政区」
後者は「地域自治区・合併特例区」または「町名の一部が○○区になっている」場合らしい。
(コメントで教えていただきました。@jkr_2255さんありがとうございました!)
区まで含むデータが約1万6600件で、市のみとするのが約1200件だった。
なので市と区が並んでいれば区まで含めるように優先させる。
(...??[都道府県])(.+?市.+?区|.+?[市区町村])(.+)
失敗数:
2783/123909 (2%)
わりと劇的に効果があった。
その代わり、失敗するほうの約1200件は名前を直接書かなきゃいけなくなった。
○○郡○○[町村]
○○郡があるなら市はない。はず。
これらを市区の判定よりも優先させたい。
(...??[都道府県])(.+?郡.+?[町村]|.+?市.+?区|.+?[市区町村])(.+)
失敗数:
2205/123909 (1%)
例外の市を書き込む
ここまでは愛と勇気と知恵と工夫だけで何とか成功率を上げてきた。
ここからは法則に頼れない泥臭い戦いとなる。
作成してきた正規表現で失敗する市を集めて直接書き込む。
→ 住所を分ける正規表現で失敗した市名を表示するやつ
(?:旭川|伊達|石狩|盛岡|奥州|田村|南相馬|那須塩原|東村山|武蔵村山|羽村|十日町|上越|富山|野々市|大町|蒲郡|四日市|姫路|大和郡山|廿日市|下松|岩国|田川|大村)市
これらの大半は、前述した「○○市△△区」を市のみとする市だ。
ついでに「四日市市」「廿日市市」「野々市市」という強敵(とも)も葬られた。
これを市区町村の判定部分の最初に書き込んで終わり。
(...??[都道府県])((?:旭川|伊達|石狩|盛岡|奥州|田村|南相馬|那須塩原|東村山|武蔵村山|羽村|十日町|上越|富山|野々市|大町|蒲郡|四日市|姫路|大和郡山|廿日市|下松|岩国|田川|大村)市|.+?郡.+?[町村]|.+?市.+?区|.+?[市区町村])(.+)
失敗数:
29/123909 (0%)
なん...だと...
残った29件 (0.0002%) に捧げる修正
失敗結果を眺めてみると、失敗したのは以下の2ヵ所だけだった。
- 群馬県|佐波郡玉村町|○○
- 佐賀県|杵島郡大町町|○○
「○○郡△△[町村]」の判定で「玉村町」が玉村「大町町」が大町と判定されてた。
「それ以降」が「町・村」で始まることはわりと多く.+?[町村]{1,2}
とすると失敗率が上がる。
仕方ないので(?:玉村|大町|.+?)[町村]
とする。
どうでもいいけど佐賀県杵島郡大町町大町って町すぎないか。
結果
(...??[都道府県])((?:旭川|伊達|石狩|盛岡|奥州|田村|南相馬|那須塩原|東村山|武蔵村山|羽村|十日町|上越|富山|野々市|大町|蒲郡|四日市|姫路|大和郡山|廿日市|下松|岩国|田川|大村)市|.+?郡(?:玉村|大町|.+?)[町村]|.+?市.+?区|.+?[市区町村])(.+)
失敗数:
0/123909 (0%)
とりあえず失敗数0にできたので終わり。
もっと短くできるという方は挑戦してみてください。
あと「なにこのクソ正規表現」というご意見もお待ちしております。
※郵便局のデータにない(古い|新しい)住所で、市区町村に「市区町村」の文字を含み、今まで見てきたような法則から外れる住所等では失敗します。
厳密に判定しなくちゃいけないとこではもっと頑張ったほうがいいと思います。
ていうかそもそも正規表現だけでやろうとするのが間違いだと思います。まる
参考リンク
-
郵便番号データの落とし穴 ←「なぜそれを 2005 年末の現在に至るまで放置しているのか」とまで書かれている問題が2016年現在も修正されてなかったり最高にクール
追記1: エクストリームモード
ここからが本当の地獄だ...
確認スクリプトの起動時に引数「-e」をつけるとエクストリームモードになります。
全国民が「都道府県市区町村ハイツ」という建物に住みます。
具体的には、番地と建物名が足されるだけです。
(壱丁目二番地3号都道府県市区町村ハイツ456号室)
さきほどの正規表現を試したら失敗率60%でした。
むしろ(.+?[都道府県])(.+?[市区町村])(.+)
のほうが失敗率低かったです。(16%)
純粋に最短マッチのみですからね。
対策?知りません。頑張ってください。
これだから住所解析はイヤなんですよ!
追記2: エクストリームモードクリア
市区町村は2〜10文字しかないから長さでガチガチに縛る
(...??[都道府県])((?:旭川|伊達|石狩|盛岡|奥州|田村|南相馬|那須塩原|東村山|武蔵村山|羽村|十日町|上越|富山|野々市|大町|蒲郡|四日市|姫路|大和郡山|廿日市|下松|岩国|田川|大村|宮古)市|...?郡(?:玉村|大町|.{1,5}?)[町村]|(?:.{1,4}市)?.{1,4}?区|.{1,7}?[市町村])(.+)
失敗数:
44 / 123909 (0%)
ぐぬぬ....区切り文字を含む郡と区を少し除外
(...??[都道府県])((?:旭川|伊達|石狩|盛岡|奥州|田村|南相馬|那須塩原|東村山|武蔵村山|羽村|十日町|上越|富山|野々市|大町|蒲郡|四日市|姫路|大和郡山|廿日市|下松|岩国|田川|大村|宮古)市|(?:余市|高市|[^市]{2,3}?)郡(?:玉村|大町|.{1,5}?)[町村]|(?:.{1,4}市)?[^町]{1,4}?区|.{1,7}?[市町村])(.+)
失敗数:
20 / 123909 (0%)
もう分かんないし例外の市を追加でいいや
(...??[都道府県])((?:旭川|伊達|石狩|盛岡|奥州|田村|南相馬|那須塩原|東村山|武蔵村山|羽村|十日町|上越|富山|野々市|大町|蒲郡|四日市|姫路|大和郡山|廿日市|下松|岩国|田川|大村|宮古|富良野|別府|佐伯|黒部|小諸|塩尻|玉野|周南)市|(?:余市|高市|[^市]{2,3}?)郡(?:玉村|大町|.{1,5}?)[町村]|(?:.{1,4}市)?[^町]{1,4}?区|.{1,7}?[市町村])(.+)
失敗数:
0 / 123909 (0%)
なにか敗北感がすごい