Skip to content

郵便番号データ(utf_ken_all.csv)を読み込んで住所を自動で分割する機能を追加 - #9

Merged
cssho merged 2 commits into
masterfrom
ken-all
Nov 5, 2026
Merged

cssho merged 2 commits into
masterfrom
ken-all

Conversation

@cssho

@cssho cssho commented Nov 5, 2026 •

Copy link
Copy Markdown
Owner

概要

日本郵便の郵便番号データ(1レコード1行、UTF-8形式)を利用者側で指定して読み込めるようにしました。データはリポジトリに同梱していません。

郵便番号の町域名で住所を住所A(町域名まで)と住所B(町域名以降)に自動で分割できるようになり、住所全体を渡しても町域名の漢数字を抜き出さなくなります。

var dictionary = ZipCodeDictionary.Load(@"c:\data\utf_ken_all.zip");
var code = BarCode.Create("1020082", "東京都千代田区一番町5-3", dictionary);
// code.AddressNumber → "5-3"(郵便番号データなしでは "1-5-3")

追加した API(コア、外部依存なし)

API 内容
ZipCodeDictionary.Load(パス) / Load(Stream) utf_ken_all.csv、または zip(utf_ken_all.zip)のまま読み込む。Load(Stream) は渡された Stream を閉じない
dictionary.IsRepresentative(郵便番号) 代表の郵便番号(町域名が「以下に掲載がない場合」)なら true。データにない番号(事業所の個別番号など)は null
dictionary.Find(郵便番号) / Contains(郵便番号) 郵便番号の町域を取得する
dictionary.TrySplitAddress(郵便番号, 住所, out 住所A, out 住所B) 町域名で住所を分割する。都道府県名の省略、町域名の前の「大字」「字」、空白、全角・半角、「ヶ/ケ」の違いは区別しない。町域名の括弧書き(「北六条東(1~7丁目)」など)は除いて照合する
BarCode.Create(郵便番号, 住所, 郵便番号データ) 分割できれば住所Bだけから抜き出す。できなければ住所全体を使う(郵便番号データなしと同じ)

町域名の特殊な値

  • 「○○市の次に番地がくる場合」「○○村一円」:町域名なしとして、市区町村名までで分割する。
  • 「一円」という実在の町域名(滋賀県犬上郡多賀町一円)もあるため、「一円」は「市区町村名 + 一円」の形のときだけ特殊な値とみなす。
  • 代表の郵便番号(「以下に掲載がない場合」):町域名がわからないため分割しない。

代表の郵便番号の判定(IsRepresentativeZipCode)
郵便番号データに基づいて判定します。データにない番号(郵便番号データより新しい番号など)は、データなしのときと同じく下2けたで判定します。

住所A・B 版の Create の変更
住所Aを取り除くときの照合を、郵便番号データと同じ方法(空白、全角・半角、「ヶ/ケ」を区別しない)にそろえました。

読み込み時のエラー
列数が違う・郵便番号が 7 桁の数値でない・UTF-8 として読めない文字がある、のいずれかで行番号付きのエラーにします。最後のチェックで、従来の Shift_JIS 形式(KEN_ALL.CSV)の取り違えを検出します。

実データでの確認

2026年10月30日更新の utf_ken_all.csv で確認しました(データはリポジトリに含めていません)。

項目 結果
読み込み 124,527 行(郵便番号 120,721 件)を約 0.2 秒、メモリ約 22MB。形式エラーなし
分割(全町域) 「都道府県 + 市区町村 + 町域 + 番地」で組み立てた住所を、代表番号以外の 122,657 町域すべてで正しく分割。都道府県を省いた場合も失敗 0 件
マニュアルの住所例 18 件 すべて分割できた。一番町・八丁堀・三ノ輪は、辞書なしでの 1-5-3・8-2-1-1・3-1-2 が 5-3・2-1-1・1-2 になった
代表番号 1,769 件。すべて下 2 けたが 00 で、00 なのに代表でない番号は 0 件
処理時間(10 万件) 分割のみ 1 件 0.99μs、辞書ありの生成 1 件 5.92μs(辞書なし 4.62μs)

全町域の分割は、データ自身から組み立てた住所での検証です。実際の住所の書き方の揺れまでは確かめていません。

コードレビューへの対応

  • 代表の郵便番号では分割しないようにした(「東京都千代田区一番町」に代表番号を使うと「一番町」の漢数字を抜き出していた)。
  • データにない番号を「代表でない」と決めつけず、下2けたで判定するようにした。
  • 住所A・B 版の Create の照合を郵便番号データと同じにした。
  • 郵便番号の正規化と検索を 1 回にまとめた。
  • 都道府県名・市区町村名の文字列を共有し、メモリを約 30MB から約 22MB に削減した。
  • 分割のたびに候補文字列を作る点は、計測の結果(10 万件で約 0.1 秒)、読み込み時に作っておくとメモリが増えるため見送った。
  • BarCode.Create(郵便番号, "…", null) が曖昧になる点は、2.0.0 が未公開で影響を受ける利用者がいないため見送った。

テスト

  • dotnet test で 176 件すべて成功(42 件追加)。
  • テスト用データ TestData/ken_all_sample.csv は、utf_ken_all.csv の形式で手書きしたもの(実データの抜粋ではない)。

🤖 Generated with Claude Code

https://claude.ai/code/session_01HNb8H3ydQTR8dWU8d28osE

cssho and others added 2 commits November 5, 2026 21:57
- ZipCodeDictionary を追加し、利用側でダウンロードした utf_ken_all.csv / .zip を読み込めるようにした
- 郵便番号の町域名で住所を住所A・住所Bに分割する TrySplitAddress を追加
  (都道府県名の省略、「大字」「字」、空白、全角・半角、ヶ/ケ の違いを吸収)
- BarCode.Create(郵便番号, 住所, 郵便番号データ) を追加し、住所Bだけから抜き出すようにした
- 郵便番号データを使う場合は、代表の郵便番号を郵便番号データに基づいて判定する
- README と CLAUDE.md を更新

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HNb8H3ydQTR8dWU8d28osE
- 代表の郵便番号(以下に掲載がない場合)では住所を分割しないようにした
- 郵便番号データにない郵便番号は、代表の郵便番号を下2けたで判定するようにした
- 住所A・B版の Create で住所Aを取り除くとき、郵便番号データと同じ照合
  (空白、全角・半角、ヶ/ケ を区別しない)を使うようにした
- 郵便番号の正規化と郵便番号データの検索を1回にまとめた
- 都道府県名・市区町村名の文字列を共有し、メモリ使用量を約30MBから約22MBに削減

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HNb8H3ydQTR8dWU8d28osE
@cssho
cssho merged commit 8ed8b8d into master Nov 5, 2026
@cssho
cssho deleted the ken-all branch November 5, 2026 13:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant