blog

配送先住所情報をインテリジェントに認識するPHP

アイデア:主なアイデアは2種類に分かれています、一つは、ユーザーが通常の入力フルアドレス、その後、マッチングの地域レベルに応じてアドレスの順序です、他のユーザーが非正規入力、その後、完全なファジー検索...

Jun 5, 2020 · 8 min. read
シェア

**機能要件:***ユーザーが配送先住所の混合物を入力すると、インテリジェントにアドレス、携帯電話、名前を識別することができます。

**地域用と姓名用の2種類の用紙が必要です。

** 主なアイデア:***主なアイデアは2種類に分かれています、1つは、ユーザーが通常、完全なアドレスを入力し、その後、マッチングの地域レベルに応じてアドレスの順序です。

**携帯電話は、自分のニーズに合わせて適切なルールを変更することができます;

アドレス照合に問題なし

レンダリング:

コード: クラスファイル

<?php
class DistinguishAddress {
/**
 * クラス・エントリー・メソッド
 * 着信住所情報は自動的に識別され、最も一致度の高いものが返される。
 * 住所が追加された場合は、キャッシュファイルを削除して再キャッシュする必要がある。
 * @param $address
 **/
function getAddressResult($address){
 // 優先される最初の方法
 $result = $this->getAddressArrar($address);
 // 結果が満足のいくものでない場合は、ファジー・デマッチを行う。
 if($result['level'] != 3){
 $result_sub = $this->addressVague($address);
 // 完全一致のみが置換され、それ以外は変更されない。
 if($result_sub['level'] == 3){
 $result = $result_sub;
 }
 }
 // -優先順位一致電話
 if(preg_match('/1\d{10}/', $address, $mobiles)){ //  
 $result['mobile'] = $mobiles[0];
 } else if(preg_match('/(\d{3,4}-)?\d{7,8}/', $address, $mobiles)){ // 固定電話番号
 $result['mobile'] = $mobiles[0];
 }
 // 名前必須スペース共有確率を特定する
 preg_match_all('/[\x{4e00}-\x{9fa5}]{2,}/iu', $address,$names);
 if($names){
 $name_where = '';
 foreach ($names[0] as $name){
 // 1文字以上5文字以下でなければならない
 if(1 < mb_strlen($name,'utf-8') && mb_strlen($name, 'utf-8') < 5){
 $sub_name = mb_substr($name, 0, 1, 'utf-8');
 $name_where .= "name like '{$sub_name}%' or ";
 }
 }
 if(!empty($name_where)){
 $name_where = substr($name_where, 0, -3);
 $names_sql = "select name from surname where {$name_where} order by sort desc";
 $list = Db::getInstance('DbTrade')->getAll($names_sql);
 // 名前の可能性を数える
 $result['name_num'] = count($list);
 if($list) {
 $name_first = $list[0]['name'];
 foreach ($names[0] as $name){
 $len = mb_strlen($name_first, 'utf-8');
 if (mb_substr($name, 0, $len, 'utf-8') == $name_first){
 $result['name'] = $name;
 }
 }
 }
 }
 }
 // 詳細内の名前と電話番号を削除する
 $result['info'] = str_replace($result['mobile'], '', $result['info']);
 $result['info'] = str_replace($result['name'], '', $result['info']);
 $result['info'] = $result['province']['region_name'] . $result['city']['region_name'] . $result['district']['region_name'] . $result['info'];
 return $this->getCityLevelList($result);
}
/**
 * 対応する都市クラスのリストを取得する
 **/
function getCityLevelList($result){
 // すべての住所の再帰リストを取得する
 $regions = $this->getRegionTreeList();
 // 都道府県のリストを取得する - 対応するリストが存在する場合のみ返す。
 $province_id = $result['province']['region_id'];
 if ($province_id) {
 foreach ($regions as $region){
 unset($region['childs']);
 $result['province_list'][] = $region;
 }
 }
 // 都市のリストを取得する - 値が存在する場合にのみ、対応するリストを返す。
 $city_id = $result['city']['region_id'];
 if ($city_id) {
 foreach ($regions[$province_id]['childs'] as $region){
 unset($region['childs']);
 $result['city_list'][] = $region;
 }
 }
 // 地域のリストを取得する - 値が存在する場合のみ対応するリストを返す。
 $district_id = $result['district']['region_id'];
 if ($district_id) {
 foreach ($regions[$province_id]['childs'][$city_id]['childs'] as $region){
 unset($region['childs']);
 $result['district_list'][] = $region;
 }
 }
 return $result;
}
/**
 * すべての住所の再帰リストを取得する
 **/
function getRegionTreeList(){
 // IO
 $file_name = 'regions.json';
 if(is_file($file_name)){
 $regions = file_get_contents($file_name);
 $regions = json_decode($regions, true);
 } else {
 $region_sql = "select region_id,region_name,parent_id from region";
 $regions = Db::getInstance('DbTrade')->getAll($region_sql);
 $regions = $this->arrayKey($regions);
 file_put_contents($file_name, json_encode($regions));
 }
 return $regions;
}
/**
 * 最初のメソッド
 * 住所リストから正確な住所を再帰的に見つける
 * @param $address
 * @return array
 **/
function getAddressArrar($address){
 // すべての住所の再帰リストを取得する
 $regions = $this->getRegionTreeList();
 // 初期化データ
 $province = $city = $district = array();
 // 最初に検索する都道府県 - 第一レベルの地域
 $province = $this->checkAddress($address, $regions);
 if($province){
 // 都市-第二レベル地域を検索する
 $city = $this->checkAddress($address, $province['list']);
 if($city){
 // 地域を検索する - 三次地域
 // チベット自治区、チベット自治区、ナグチュ市、ゼニ区、遼寧省南路、チベット高速道路 このアドレスの競合のため、第三パラメータはキャンセル必須
 $district = $this->checkAddress($address, $city['list']);
 }
 }
 return $this->getAddressInfo($address, $province, $city, $district);
}
 /**
 * 第2メソッド
 * 住所あいまい検索
 **/
function addressVague($address){
 $res = preg_match_all('/\S{2}[市町村 郡 町村 島嶼 州]/iu', $address,$arr);
 if(!$res) return false;
 $where = ' where ';
 foreach ($arr[0] as $value){
 if(strpos($value, 'セル')=== false && strpos($value, '開発地域')=== false){
 $where .= "region_name like '%{$value}' or ";
 }
 }
 $where = substr($where,0,-3);
 $region_sql = "select region_id,region_name,parent_id,region_type from region " . $where;
 $citys = $GLOBALS['db']->getAll($region_sql);
 // すべての住所に一致させる
 $result = array();
 foreach ($citys as &$city){
 // 関連するすべての地域ID
 $city_ids = array();
 if($city['region_type'] == 2) {
 $city_ids = array($city['parent_id'], $city['region_id']);
 // 第3レベルにマッチするか試してみる
 $region_sql = "select region_id,region_name,parent_id,region_type,left(region_name,2) as ab_name from region where parent_id='{$city['region_id']}'" ;
 $areas = $GLOBALS['db']->getAll($region_sql);
 foreach ($areas as $row){
 if(mb_strpos($address,$row['ab_name'])){
 $city_ids[] = $row['region_id'];
 }
 }
 } else if($city['region_type'] == 3){
 $region_sql = "select parent_id from region where region_id='{$city['parent_id']}'" ;
 $city['province_id'] = $GLOBALS['db']->getOne($region_sql);
 $city_ids = array($city['parent_id'], $city['region_id'], $city['province_id']);
 }
 // この単語に関連するすべての地域レコードを検索する
 $where = " where region_id in(" . join(',', $city_ids) . ")";
 $region_sql = "select region_id,region_name,parent_id,region_type,left(region_name,2) as ab_name from region " . $where . ' order by region_id asc';
 $city_list = $GLOBALS['db']->getAll($region_sql);
 sort($city_ids);
 $key = array_pop($city_ids);
 $result[$key] = $city_list;
 sort($result);
 }
 if($result){
 list($province, $city, $area) = $result[0];
 return $this->getAddressInfo($address, $province, $city, $area);
 }
 return false;
}
/**
 * 正しい市区町村の住所に一致させる
 * @param $address
 * @param $city_list
 * @param int $force
 * @param int $str_len
 * @return array
 **/
function checkAddress($address, $city_list, $force=false, $str_len=2){
 $num = 0;
 $list = array();
 $result = array();
 // すべての可能な都市を反復処理する
 foreach ($city_list as $city_key=>$city){
 $city_name = mb_substr($city['region_name'], 0, $str_len,'utf-8');
 // ストレージに現在の住所文字が含まれているかどうかを判断する
 $city_arr = explode($city_name, $address);
 // 関連ワードが存在する場合、アドレスのすべてのサブアドレスを保持する
 if(count($city_arr) >= 2){
 // 名前の長さと現在の比較の長さの両方を持たなければならない
 if(strlen($city['region_name']) < $str_len){
 continue;
 }
 $num ++;
 $list = $list + $city['childs'];
 $result[] = array(
 'region_id' => $city['region_id'],
 'region_name' => $city['region_name'],
 'list' =>$list,
 );
 }
 }
 // 複数存在する場合は、文字照合の長さを長くする
 if($num > 1 || $force){
 $region_name1 = $result[0]['region_name'];
 $region_name2 = $result[1]['region_name'];
 if(strlen($region_name1) == strlen($region_name2) && strlen($region_name1) == $str_len){
 $region_id1 = $result[0]['region_id'];
 $region_id2 = $result[1]['region_id'];
 $index = $region_id1 > $region_id2 ? 1 : 0;
 $result = $result[$index];
 return $result;
 }
 return $this->checkAddress($address, $city_list, $force, $str_len+1);
 } else {
 $result[0]['list'] = $list;
 return $result[0];
 }
}
/**
 * 元の住所に基づいて詳細を返す
 * @param $address
 * @param $province
 * @param $city
 * @param $area
 * @return array
 **/
function getAddressInfo($address, $province, $city, $district){
 // 住所の最後の出現箇所を検索する - 詳細を傍受する
 $find_str = '';
 if($province['region_name']){
 $find_str = $province['region_name'];
 if($city['region_name']){
 $find_str = $city['region_name'];
 if($district['region_name']){
 $find_str = $district['region_name'];
 }
 }
 }
 // インターセプト詳細
 $find_str_len = mb_strlen($find_str,'utf-8');
 for($i=0; $i<$find_str_len-1; $i++){
 $substr = mb_substr($find_str,0,$find_str_len - $i, 'utf-8');
 $end_index = mb_strpos($address, $substr);
 if ($end_index){
 $address = mb_substr($address, $end_index + mb_strlen($substr) , mb_strlen($address) - $end_index);
 }
 }
 !empty($find_str) && $find_str = '|\S*' . $find_str;
 $area['info'] = preg_replace("/\s*|,|,|:| {$find_str}/i", '', $address);
 $level = 0;
 if($district['region_name']){
 $level = 3;
 } else if($city['region_name']){
 $level = 2;
 } else if ($province['region_name']) {
 $level = 1;
 }
 return array(
 'province' => array('region_id'=>$province['region_id'], 'region_name'=>$province['region_name']),
 'city' => array('region_id'=>$city['region_id'], 'region_name'=>$city['region_name']),
 'district' => array('region_id'=>$district['region_id'], 'region_name'=>$district['region_name']),
 'info' => $area['info'],
 'level' => $level,
 );
}
/**
 * すべての住所を無限ソート配列に再帰する
 * @param $data
 * @param int $region_id
 * @return array
 **/
function arrayKey($data, $region_id=1){
 $result = array();
 foreach ($data as $row){
 if($region_id == $row['parent_id']){
 $key = $row['region_id'];
 $row['childs'] = $this->arrayKey($data, $row['region_id']);
 $result[$key] = $row;
 }
 }
 return $result;
}
}
?>

苗字表

DROP TABLE IF EXISTS `surname`;
CREATE TABLE `surname` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` char(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
 `sort` int(11) NULL DEFAULT NULL,
 PRIMARY KEY (`id`) USING BTREE,
 INDEX `name`(`name`) USING BTREE,
 INDEX `sort`(`sort`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 481 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '名字テーブル' ROW_FORMAT = Compact;

アドレス領域テーブル

CREATE TABLE `region` (
 `region_id` smallint(5) UNSIGNED NOT NULL AUTO_INCREMENT,
 `parent_id` smallint(5) UNSIGNED NOT NULL DEFAULT 0,
 `region_name` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
 `region_type` tinyint(1) NOT NULL DEFAULT 2,
 `agency_id` smallint(5) UNSIGNED NOT NULL DEFAULT 0,
 PRIMARY KEY (`region_id`) USING BTREE,
 INDEX `parent_id`(`parent_id`) USING BTREE,
 INDEX `region_type`(`region_type`) USING BTREE,
) ENGINE = InnoDB AUTO_INCREMENT = 395
Read next

Array.fill()を使ってオブジェクトを埋める際の問題点

これではうまくいかないので、mapを使ってデータを埋めます。 最終的な解決策は、まず配列の各項目をnullで埋めて、配列が長さだけでなくデータ項目を持つようにし、次にmapを使って各項目をnewObject()で置き換えます。

Jun 5, 2020 · 1 min read

HashMapソースコード

Jun 3, 2020 · 8 min read

js_tree_structure_to_nested_structure

Jun 3, 2020 · 1 min read

WebAssemblyの初見

Jun 2, 2020 · 2 min read