org.unlaxer.jaddress.parser.ParsingState Maven / Gradle / Ivy
package org.unlaxer.jaddress.parser;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.unlaxer.util.collection.ID;
import org.unlaxer.util.collection.IDAccessor;
public enum ParsingState implements IDAccessor{
/*
# 住所文字列の階層正規化
* 郵便番号から住所jpのレコードを取得する
* もし住所jpにレコードが存在しなければエラーにする
* レコードは複数存在することもある
* 大阪の地区で確認したところ以下の通り
* townaName:["虫取","虫取町"]
* townaName:["木戸","木戸町"]
* townaName:["天神橋] , blockName:["7丁目","8丁目"] <-日本一長い商店街
* townaName:["曽根町","南曾根"]
* townaName:["三原台"] , isDeleted:[true , false] <-これはisDeleted:falseの物だけ抜き出すべき条件
* 住所jpのtownNameを取得する
* 対象住所からtownNameを探し二分割する
* 対象データの一文字目(左から)から探す
* もし対象住所内にtownNameが無ければエラーにする
* 町名迄と丁目以降に分かれる
* 町名から丁目の階層種類をDBを用いて求める
* DBは電力会社の住所群をindexingする時に生成する
* 後述
* 丁,丁番,丁番号 等の階層数
* 丁によっては階層数は違うことがあるか?
* その場合は町名+丁や町名+丁番等で階層数の解決を行う
* 丁目以降のtokenizeを行う
* 階層数によってtokenizeの回数が変化する
* 丁目の文字列を取得する
* 以下の文字列をdelimitorとして使用する
* 町域Top1
* 丁
* - ー ― ~ ~ ・ , 、,<全角space>
* 番地の文字列を取得する
* 以下の文字列をdelimitorとして使用する
* 番地
* 番
* - ー ― ~ ~ ・ , 、,<全角space>
* 号の文字列を取得する
* 以下の文字列をdelimitorとして使用する
* 号
* - ー ― ~ ~ ・ , 、,<全角space>
* 丁目以降文字列をまだ消費しきっていない場合、以降は建物名などがある
* 丁目番地号から建物種類と建物名の配列をDBを用いて求める
* 建物種類
* 一戸建て
* アパート/マンション
* 階あり
* 階表現種類
* 203-> 数字
* 2F -> F以前
* 2階 -> 階以前
* 棟あり
* 棟種類
* A棟 -> 棟以前
* A203 -> 部屋番号以前
* 部屋番号種類
* 203-> 数字
* a -> アルファベット
* その他
* DBは電力会社の住所群をindexingする時に生成する
* 後述
* 建物以降のtokenizeを行う
* DBから取得した建物名の配列をdelimitorとして分割をする
* 分割ができた場合は以降を棟番号、部屋番号の文字列として扱う
* 棟番号、部屋番号の文字列がパース出来なかった場合、以下のケースも考慮
* 大阪府 大阪市浪速区 戎本町1-5-18-403 sonix2015
* 分割ができない場合は以下の様にtokenizeを行う
* - ー ― ~ ~ ・ , 、,<全角space>
* 文字列種類によってtokenize
* 棟や号でtokenize
* 棟や号の前で同じ文字種類の物をtokenとする
* サンヴァリエ金岡11棟203号 -> 11棟と203号
* いきなり数字が来る ->
* 例)戎本町1-5-18-403 -> 部屋番号もしくはダミーサフィックス
* 建物種類から部屋番号かダミーサフィックスかを判定する
* いきなり数字が来る ->
* 例)戎本町1-5-18-403 -> 部屋番号もしくはダミーサフィックス
* 建物種類から部屋番号かダミーサフィックスかを判定する
*
*
*
* delimitorは完全マッチではなく表記ゆれに対応する
* サンヴァリエ -> サンヴァリエ or サンバリエ
* 簡単にはLevensteinDistance , JaroWinklerDistance
* delimitorは文字列種類を考慮する?
* 鉄鋼団地1から鉄鋼団地12まであるとして
* 市川市2-3 鉄鋼団地12-305という住所に対してdelimitor
* delimitor鉄鋼団地1を使用すると
* 市川市2-3 と2-305に分かれる
* delimitor鉄鋼団地12が使用できるなら先に使用した方が良い
* estimatedSuffixを返せるようにするとよいか?この場合2を返す
* google検索
* googleのindexから検索をかける
* 直接homesとかsuumoも検索をかけると蹴られる可能性があるから
* 住所と共に建物名を入れて検索をする
* 大阪府東大阪市衣摺2-6-26 ヴェルデパティオC みたいな感じで
* 検索結果は10件ほどだと思われるが、タイトルにそのものずばりがあればその建物は存在すると思われる
* 住所が含まれなければマッチしないと考える
* 検索結果にはサイト情報があるので、homesとか、suumoとか不動産会社かどうかの判別ができる
* 不動産会社でなければ建物名でない可能性があるのでマッチしていないと考える
* マッチしなかったら、site:https://www.homes.jp の様にそれぞれのsiteで絞り込んで検索をする
* 前提として不動産サイトは空き部屋を登録しているので、すべての部屋が埋まっている場合はマッチしない
* 非正規化文章からの住所/建物名のマッチチングについて
* 検索エンジン内では、同一正規化ロジックで文章と検索語を正規化するので問題が起きにくい
* googleの検索結果など文章が正規化されてないものから検索をする場合もある
* 検索語を表記ゆれさせて複数の単語で完全一致を行う
* 文章を切り出して、StringDistance(LevensteinDistance , JaroWinklerDistance)を求めて閾値以上の物を抜き出す
* 参考
* word2vec
* https://deepage.net/bigdata/machine_learning/2016/09/02/word2vec_power_of_word_vector.html
* https://deepinsider.jp/issue/deeplearningnext/word2vecrev
*/
パース開始(StateKind.start , "start" ,
new StateSpecifier("入力をValidate")
),
入力をValidate(StateKind.duringProcessing , "validateInputs" ,
new StateSpecifier("ZIPから郵便番号情報取得") ,
new StateSpecifier("入力Address組み合わせエラー")
),
入力Address組み合わせエラー(StateKind.error , "errorOnValidateAddresses" ,
List.of("階層要素を指定してaddress配列を入力として渡すとき、階層要素.不明を指定している場合、すべての階層要素は不明でなければえらーとする")
),
ZIPから郵便番号情報取得(StateKind.duringProcessing , "getJyuusyoJpsFromZip" ,
new StateSpecifier("郵便番号情報取得失敗エラー") ,
new StateSpecifier("郵便番号情報町名取得失敗エラー") ,
new StateSpecifier("都道府県から町名までを分割する")
),
郵便番号情報取得失敗エラー(StateKind.error , "errorOnGetJyuusyoJpFromZip"),
郵便番号情報町名取得失敗エラー(StateKind.error , "errorOnGetCyoumeiInJyuusyoJpFromZip"),
都道府県から町名までを分割する(StateKind.duringProcessing ,"splitAddressFromPrefectureToTownName" ,
new StateSpecifier("検索住所配列を町名を元に二分割する")
),
検索住所配列を町名を元に二分割する(StateKind.duringProcessing , "splitAddressWithTownName" ,
List.of("対象データの一文字目(左から)から探す","もし対象住所内にtownNameが無ければエラーにする","町名迄と丁目以降に分かれる") ,
new StateSpecifier("町名分割エラー"),
new StateSpecifier("町名から丁目の階層種類をDBを用いて求める")
),
町名分割エラー(StateKind.error , "errorOnSlitAddressWithTownName" ,
List.of("対象データの一文字目(左から)から探す","もし対象住所内にtownNameが無ければエラーにする","町名迄と丁目以降に分かれる")
),
町名から丁目の階層種類をDBを用いて求める(StateKind.duringProcessing , "resolveHierarchyFromTownName" ,
List.of("階層種類DBは電力会社の住所群をindexingする時に生成する"),
new StateSpecifier("丁目以降を分割する"),
new StateSpecifier("丁目以降から部屋番号まで分割する")
),
丁目以降から部屋番号まで分割する(StateKind.duringProcessing,"splitBlockAndBuildings",
new StateSpecifier("丁目から枝番までを分割する")
),
丁目以降を分割する(StateKind.duringProcessing , "splitAfterBlockAddresses" ,
new StateSpecifier("パース終了")
),
丁目から枝番までを分割する(StateKind.duringProcessing , "splitBlockAddresses" ,
new StateSpecifier("都道府県から枝番までで建物階層と建物名をDBを用いて求める")
),
都道府県から枝番までで建物階層と建物名をDBを用いて求める(StateKind.duringProcessing , "resolveBuildingHierarchy" ,
new StateSpecifier("建物名の抜き出しをする")
),
建物名の抜き出しをする(StateKind.duringProcessing , "pickupBuilding" ,
new StateSpecifier("建物より後の分割をする"),
new StateSpecifier("パース終了")
),
建物より後の分割をする(StateKind.duringProcessing , "TokenizeAfterBuilding" ,
new StateSpecifier("建物より後のTokenをmappingする")
),
建物より後のTokenをmappingする(StateKind.duringProcessing , "mappingToken" ,
new StateSpecifier("パース終了")
),
パース終了(StateKind.success , "success")
;
public final String label;
public final List nextStates;
public final String[] text;
public final int stateNumber;
public final StateKind stateKind;
public final ID id;
public boolean hasNextStates() {
return nextStates != null;
}
static Map> stateByLabel = new HashMap<>();
private ParsingState(StateKind stateKind , String label) {
this(stateKind, label , Collections.emptyList() , Collections.emptyList());
}
private ParsingState(StateKind stateKind , String label , String... nextStates) {
this(stateKind, label , Collections.emptyList() , to(nextStates));
}
private ParsingState(StateKind stateKind , String label , List text) {
this(stateKind , label , text , Collections.emptyList());
}
private ParsingState(StateKind stateKind , String label , List text , String... nextStates) {
this(stateKind , label , text , to(nextStates));
}
private ParsingState(StateKind stateKind , String label , StateSpecifier... nextStates) {
this(stateKind, label, Collections.emptyList() , Stream.of(nextStates).collect(Collectors.toList()));
}
private ParsingState(StateKind stateKind , String label , List text , StateSpecifier... nextStates) {
this(stateKind, label, text , Stream.of(nextStates).collect(Collectors.toList()));
}
private ParsingState(StateKind stateKind , String label , List text , List nextStates) {
this.label = label;
this.text = text.toArray(new String[] {});
this.stateKind = stateKind;
this.nextStates = nextStates;
stateNumber = 1>>ordinal();
id = ID.of(this);
}
public boolean isSuccess() {
return stateKind == StateKind.success;
}
public static Optional fromLabel(String label) {
return stateByLabel.computeIfAbsent(label, _label->{
for(ParsingState state : values()) {
if(state.label.equals(_label)) {
return Optional.of(state);
}
}
return Optional.empty();
});
}
public static enum StateKind{
start,
duringProcessing,
success,
error,
}
public boolean canTransit(ParsingState transitTo) {
for(StateSpecifier nextState : nextStates) {
if(nextState.get().label.equals(transitTo.label)){
return true;
}
}
return false;
}
@Override
public ID id() {
return id;
}
static List to(String... nextStates){
return nextStates == null ?
Collections.emptyList():
Stream.of(nextStates)
.map(StateSpecifier::new)
.collect(Collectors.toList());
}
} © 2015 - 2025 Weber Informatics LLC | Privacy Policy