2015年7月31日金曜日

【IT】日本郵便提供の高難易度パズル全国郵便番号を解こうとチャレンジしてみた【無理ゲー】

今日は積極的に煽っていくスタイルで。

プログラマをやってもう結構立ちますが、ここ近年では日本郵便が公開している全国版郵便番号情報の難易度が高いというのはもはや関係者周知の事実のようなのです。

郵便番号から住所を引くサービスやそれらのデータをダウンロードできるサービスなどなどたくさん出ています。

ただ、どこを探してもあのパズルデータをとりあえず使える状態に加工するソースコードが公開されていません。

外のサービスを利用できない場合もありこれはこれで困る(困ってた)ので、とりあえずどんな要件かわからないけど何段階かあるパズルのうちの最初の一つを解いてみよう、ということで今回のこのネタになりました。

例題くらいあってもいいよね的な。

サンプルソースはgistに上がっています。

なぜ郵便番号なのに難易度が高いの?


そもそもなんで郵便番号と住所を結びつけるだけのデータで難易度が高いと言われているのか。

これはたった一つの理由で
公開されているCSVの質が悪い
パズルの問題数が多い
 の一言に尽きます。

幾つか設問をご紹介しましょう。
(これが日本郵政仕様というわけではありません。設問風に煽っているだけです。)

第一問 
同じ地区のレコードが複数ある。これは住所文字列が長くなりすぎため特定文字数で分割し、DBに格納しただけのものであり、CSV上でも同様に出力される。利用者は必要に応じて連結すること。
但し、指定の文字長に満たないものも過去の経緯から分割されたままである。

第二問
Web上での一覧や紙に印刷した場合のことをだけ想定し、郵便番号で特定できる地域以外の広域には「以下に掲載がない場合」が町名に記載されている。利用者は必要に応じて加工しなさい。

第三問
ある地域では特定の番地、丁目を省くことがある。この場合町名には「xxは除く」などのように追記した。利用者は必要に応じて加工しなさい。

第四問
町名に記載する番地は範囲を示す「〜」で記載されたものもある。利用者は必要に応じて加工しなさい。なお「〜」で示す番地・丁目には粒度は記載していない。利用者は必要に応じて調査しなさい。

第五問
町名の()で括られた部分は補足事項である。用途は様々であり、大規模ビルのフロア、複数に跨る場合はそれら地域名、別名、その他。利用者は必要に応じて加工しなさい。

探せばもっと細かいものはあると思いますがひとまずこんなところで。

で、このうち第二問〜第五問はどういう風に住所を表示したいのか、もう少し掘り下げると「単なる表示で使いたい」のか「入力補助で使いたい」のかで扱いが変わってくると考えています。

なので、これらはその問題を解決しなければならない時に解こうと思います。

今回のターゲットは第一問にしましょう。

第一問は何が問題なのか

公式にはこうあります。
※7 全角となっている町域名の文字数が38文字を超える場合、また、半角カタカナとなっている町域名のフリガナが76文字を越える場合には、複数レコードに分割しています。
出典 http://www.post.japanpost.jp/zipcode/dl/readme.html
DBの設計ミスったな?これくらいで大丈夫だろwみたいなとり方したな?

ただ、分割されているレコードには分割用の連番もフラグもありません。
じゃあどうやって判別するのか。

  1. 13番目のフラグ「一つの郵便番号で二以上の町域を表す場合の表示」が「0:複数ある」場合
  2. そのレコードの下にあるものが同一の郵便番号で、1.の条件も満たしている場合
のように見えます。
実際には38文字以下なのに分割されていたりするデータもあるので桁数判定はほぼ役に立たない状態です。

ちなみに、公式の仕様書にはこのあたりの連結について特に記載はありません

この状態のデータであるため、ダウンロードしたデータは行の前行・後行とも結合性が非常に強く、うっかりソートとかしてしまうと分割レコードが行方不明になるので注意が必要です。

実際どんなデータが入ってくるか、というとこんな感じです。

02405,"033  ","0330071","アオモリケン","カミキタグンロクノヘマチ","イヌオトセ(ウチカナヤ、ウチヤマ、オカヌマ、カナザワ、カナヤ、カミサビシロ、キコシ、ゴンゲンサワ、","青森県","上北郡六戸町","犬落瀬(内金矢、内山、岡沼、金沢、金矢、上淋代、木越、権現沢、",1,1,0,0,0,0

02405,"033  ","0330071","アオモリケン","カミキタグンロクノヘマチ","シキ、シチヒャク、シモクボ<174ヲノゾク>、シモサビシロ、タカモリ、ヅメキ、ツボケザワ<2","青森県","上北郡六戸町","四木、七百、下久保「174を除く」、下淋代、高森、通目木、坪毛沢「2",1,1,0,0,0,0

02405,"033  ","0330071","アオモリケン","カミキタグンロクノヘマチ","5、637、641、643、647ヲノゾク>、ナカヤシキ、ヌマクボ、ネコハシ、ホリキリ","青森県","上北郡六戸町","5、637、641、643、647を除く」、中屋敷、沼久保、根古橋、堀切",1,1,0,0,0,0

02405,"033  ","0330071","アオモリケン","カミキタグンロクノヘマチ","サワ、ミナミタイ、ヤナギサワ、オオマガリ)","青森県","上北郡六戸町","沢、南平、柳沢、大曲)",1,1,0,0,0,0


02405,"033  ","0330072","アオモリケン","カミキタグンロクノヘマチ","オリモ(イマクマ<213-234、240、247、262、266、27","青森県","上北郡六戸町","折茂(今熊「213〜234、240、247、262、266、27",1,1,0,0,0,0

02405,"033  ","0330072","アオモリケン","カミキタグンロクノヘマチ","5、277、280、295、1199、1206、1504ヲノゾク>、","青森県","上北郡六戸町","5、277、280、295、1199、1206、1504を除く」、",1,1,0,0,0,0

02405,"033  ","0330072","アオモリケン","カミキタグンロクノヘマチ","オオハラ、オキヤマ、カミオリモ<1-13、71-192ヲノゾク>)","青森県","上北郡六戸町","大原、沖山、上折茂「1−13、71−192を除く」)",1,1,0,0,0,0

わかりにくいですね。
町名だけを抜き出すとこんな感じです。


"犬落瀬(内金矢、内山、岡沼、金沢、金矢、上淋代、木越、権現沢、"
"四木、七百、下久保「174を除く」、下淋代、高森、通目木、坪毛沢「2"
"5、637、641、643、647を除く」、中屋敷、沼久保、根古橋、堀切"
"沢、南平、柳沢、大曲)"

"折茂(今熊「213〜234、240、247、262、266、27"
"5、277、280、295、1199、1206、1504を除く」、"
"大原、沖山、上折茂「1−13、71−192を除く」)"

上の場合は4レコードが連結対象、下の場合は3レコードが連結対象ということでしょう。

犬落瀬(内金矢、内山、岡沼、金沢、金矢、上淋代、木越、権現沢、四木、七百、下久保「174を除く」、下淋代、高森、通目木、坪毛沢「25、637、641、643、647を除く」、中屋敷、沼久保、根古橋、堀切沢、南平、柳沢、大曲)

折茂(今熊「213〜234、240、247、262、266、275、277、280、295、1199、1206、1504を除く」、大原、沖山、上折茂「1−13、71−192を除く」)

こんな感じの一連になるかと思います。

結果、表示的には犬落瀬内金矢、犬落瀬内山のような感じになるのでしょうが、それは第二問〜第五問あたりの扱いになるので第一問はここまでで解答とします。

ざっくりサンプルのソースコードを書いてみました。
例外処理もエラーも何も拾っていないので気をつけてください。

CSVを読んでjsonを吐き出しますが、元のファイルの2.5倍くらいになります。
これも気をつけてください。

JISの地方公共番号も出力してます。フィールド'code'がそれです。
これを使って都道府県でファイルを分けるとか、郵便番号上3桁でファイルを分けるとか適宜改造してください。

0 件のコメント :

コメントを投稿