特殊比較:ゼロ比較
_compare0関数はある程度の違いを吸収した実用的比較関数。
<?php
$keys = ['neko','yagi','inu','kani','sake','nomi','toki','usi','hituji','abu','ari'];
$aryA = ['neko'=>'0','inu'=>'','yagi'=>0,'kani'=>null,'sake'=>false,'nomi'=>'ノミ','usi'=>1,'hituji'=>'1','abu'=>'1.0','ari'=>true,'toki'=>''];
$aryB = ['neko'=>'0','inu'=>'','yagi'=>0,'kani'=>null,'sake'=>false,'nomi'=>'ノミ','usi'=>1,'hituji'=>'1','abu'=>'1.0','ari'=>true];
echo "<table class='tbl2'><thead><tr><th>A</th><th>B</th><th>一致</th></tr></thead><tbody>";
$output = array();
foreach($keys as $key){
$a_value = null;
if(isset($aryA[$key])) $a_value = $aryA[$key];
foreach($keys as $key_b){
$b_value = null;
if(isset($aryB[$key_b])) $b_value = $aryB[$key_b];
$res = _compare0($a_value,$b_value);
output($key,$a_value,$key_b,$b_value,$res);
}
}
echo "</tbody></table>";
echo '<br>----<br>';
if('ノミ'==0){
echo '一致';
}else{
echo '不一致';
}
/**
* ゼロ比較
*
* @note
* 比較用のカスタマイズ関数。
* ただし、空の値の比較は0とそれ以外の空値(null,"",falseなど)で仕様が異なる。
* 0とそれ以外の空値(null,"",falseなど)は不一致のみなす。
* 0と'0'は一致と判定する。
* null,'',falseのそれぞれの組み合わせは一致である。
* bool型のtrueは数字の1と同じ扱い。(※通常、2や3でもtrueとするが、この関数では1だけがtrue扱い)
* 1.0 , 1 , '1' など型が異なる数値を一致と判定する。
*
* @param $a_value
* @param $b_value
* @return bool false:不一致 , true:一致
*/
function _compare0($a_value,$b_value){
if(empty($a_value) && empty($b_value)){
if($a_value === 0 || $a_value === '0'){
if($b_value === 0 || $b_value === '0'){
return true;
}else{
return false;
}
}else{
if($b_value === 0 || $b_value === '0'){
return false;
}else{
return true;
}
}
}else{
if(gettype($a_value) == 'boolean'){
if($a_value){
$a_value = 1;
}else{
$a_value = 0;
}
}
if(gettype($b_value) == 'boolean'){
if($b_value){
$b_value = 1;
}else{
$b_value = 0;
}
}
if(is_numeric($a_value) && is_numeric($b_value)){
if($a_value == $b_value) return true;
}else{
if($a_value === $b_value) return true;
}
}
return false;
}
function output($key,$a_value,$key_b,$b_value,$res){
if(gettype($a_value) == 'string') $a_value = "'" . $a_value . "'";
if(gettype($b_value) == 'string') $b_value = "'" . $b_value . "'";
if(gettype($a_value) == 'boolean'){
if($a_value){
$a_value = 'true';
}else{
$a_value = 'false';
}
}
if(gettype($b_value) == 'boolean'){
if($b_value){
$b_value = 'true';
}else{
$b_value = 'false';
}
}
$a_str = "{$key}=>{$a_value}";
$b_str = "{$key_b}=>{$b_value}";
$res_str = "<span class='text-success'>〇</span>";
if(empty($res)) $res_str = "<span class='text-danger'>×</span>";
echo "<tr><td>{$a_str}</td><td>{$b_str}</td><td>{$res_str}</td></tr>";
}
?>
検証
/**
* ゼロ比較
*
* @note
* 比較用のカスタマイズ関数。
* ただし、空の値の比較は0とそれ以外の空値(null,"",falseなど)で仕様が異なる。
* 0とそれ以外の空値(null,"",falseなど)は不一致のみなす。
* 0と'0'は一致と判定する。
* null,'',falseのそれぞれの組み合わせは一致である。
* bool型のtrueは数字の1と同じ扱い。(※通常、2や3でもtrueとするが、この関数では1だけがtrue扱い)
* 1.0 , 1 , '1' など型が異なる数値を一致と判定する。
*
* @param $a_value
* @param $b_value
* @return bool false:不一致 , true:一致
*/
function _compare0($a_value,$b_value){
if(empty($a_value) && empty($b_value)){
if($a_value === 0 || $a_value === '0'){
if($b_value === 0 || $b_value === '0'){
return true;
}else{
return false;
}
}else{
if($b_value === 0 || $b_value === '0'){
return false;
}else{
return true;
}
}
}else{
if(gettype($a_value) == 'boolean'){
if($a_value){
$a_value = 1;
}else{
$a_value = 0;
}
}
if(gettype($b_value) == 'boolean'){
if($b_value){
$b_value = 1;
}else{
$b_value = 0;
}
}
if(is_numeric($a_value) && is_numeric($b_value)){
if($a_value == $b_value) return true;
}else{
if($a_value === $b_value) return true;
}
}
return false;
}
ツリー構造データ関連クラス
サンプル
<?php
$data = [
['id'=>1,'name'=>'ネコ','par_id'=>6],
['id'=>2,'name'=>'生物','par_id'=>0],
['id'=>3,'name'=>'動物','par_id'=>2],
['id'=>4,'name'=>'脊椎動物','par_id'=>3],
['id'=>5,'name'=>'無脊椎動物','par_id'=>3],
['id'=>6,'name'=>'哺乳類','par_id'=>4],
['id'=>7,'name'=>'爬虫類','par_id'=>4],
['id'=>8,'name'=>'魚類','par_id'=>4],
['id'=>9,'name'=>'昆虫','par_id'=>4],
['id'=>10,'name'=>'ムカデ','par_id'=>5],
['id'=>11,'name'=>'カマキリ','par_id'=>9],
['id'=>12,'name'=>'UMA','par_id'=>2],
['id'=>13,'name'=>'カッパ','par_id'=>12],
['id'=>14,'name'=>'ライオン','par_id'=>6],
['id'=>15,'name'=>'イグアナ','par_id'=>7],
['id'=>16,'name'=>'植物','par_id'=>2],
['id'=>17,'name'=>'ダイコン','par_id'=>16],
['id'=>18,'name'=>'キャベツ','par_id'=>16],
['id'=>19,'name'=>'テラピア','par_id'=>8],
['id'=>20,'name'=>'ガーラ','par_id'=>8],
['id'=>21,'name'=>'カブトムシ','par_id'=>9],
['id'=>22,'name'=>'タイワンカブト','par_id'=>21],
['id'=>23,'name'=>'ヘラクレス','par_id'=>21],
['id'=>24,'name'=>'ブタ','par_id'=>6],
];
require_once 'TreeStructureData.php';
$treeStructureData = new TreeStructureData();
$option = array(
'res_structure'=>4,
'sort_field'=>'name',
'html_tbl_class' => 'tbl2',
'html_tbl_fields' => array('id','name'),
);
// 子ノードリストをセットする
$data = $treeStructureData->tree($data,$option);
if(is_array($data)){
debug($data);
}else{
echo $data;
}
ツリー構造データ関連クラス
<?php
/**
* ツリー構造データ関連クラス
*
* @note
* 2次元型のツリー構造データに対応。
* 2次元型のツリー構造データは親ID型を持つエンティティの配列である。
*
* @author kenji uehara
* @date 2018-4-17
* @version 1.0
*
*/
class TreeStructureData{
/**
* ツリー変換
*
* @note
* 2次元ツリー構造データにツリー構造に関連したプロパティである子ノードリスト、世代(オフセットX)、オフセットY,兄弟番号
* などをセットする。オプションのレスポンスデータタイプにレスポンスのデータ構造を選べる。
*
* @param array $data 2次元ツリー構造データ 親IDを含むデータ
* @param array $option オプション
* - res_structure レスポンスデータタイプ デフォ→0
* 0:(エンティティ配列型 デフォ) ,
* 1:キーがidの構造 ,
* 2:ツリー構造 ,
* 3:2次元マップ構造
* - sort_field 子ノードソートフィールド デフォ→空 子ノードをソートするフィールド。 空ならソートしない。(デフォ)
* - sort_order 子ノードソート向き デフォ→0 0:昇順 (デフォ), 1:降順
* - id_field IDのフィールド名 デフォ→id
* - par_id_field 親IDのフィールド名 デフォ→par_id
* - child_ids_field 子ノードIDリストのフィールド名 デフォ→child_ids
* - child_nodes_field 子ノードリストのフィールド名 デフォ→child_nodes
* - bros_no_field 兄弟番号のフィールド名 デフォ→bros_no
* - gene_no_field 世代のフィールド名 デフォ→gene_no
* - offset_x_field オフセットXのフィールド名 デフォ→offset_x
* - offset_y_field オフセットYのフィールド名 デフォ→offset_y
* - html_tbl_class htmlテーブルのclass属性 デフォ→空
* - html_tbl_fields htmlテーブルの表示フィールド(配列指定可能) デフォ→id
*/
public function tree(&$data,$option = array()){
if(empty($data)) return $data;
// オプションの初期化
$option = $this->initOption($option);
$res_structure = $option['res_structure']; // レスポンスデータタイプ
// 子ノードリストをセットする
$data = $this->setChilds($data,$option);
// キーid構造に変換する
$datax = $this->convStructKeyId($data,$option);
// 世代(オフセットX)、オフセットY、兄弟番号をセットする。
$datax = $this->setOffsetsX($datax,$option);
switch ($res_structure){
case 0:
// キーID構造からエンティティ配列構造に戻す
$data = $this->convToStructNormal($datax);
return $data;
case 1:
return $datax;
case 2:
$treeData = $this->convToStructTreeX($datax,$option); // ツリー構造データに変換する
return $treeData;
case 3:
$map = $this->convToMap($datax,$option);// 2次元マップに変換する
return $map;
case 4:
$map = $this->convToMap($datax,$option);// 2次元マップに変換する
$html = $this->createHtmlTbl($map,$option); // 2次元マップからHTMLテーブルのソースコードを作成
return $html;
}
return $datax;
}
/**
* オプションの初期化
* @param array $option オプション
* @return array 初期化後のオプション
*/
private function initOption($option){
if(empty($option)) $option =array();
if(empty($option['res_structure'])) $option['res_structure'] = 0; // レスポンスデータタイプ
if(empty($option['sort_field'])) $option['sort_field'] = ''; // 子ノードソートフィールド
if(empty($option['sort_order'])) $option['sort_order'] = 0; // 子ノードソート向き
if(empty($option['id_field'])) $option['id_field'] = 'id'; // IDのフィールド名
if(empty($option['par_id_field'])) $option['par_id_field'] = 'par_id'; // 親IDのフィールド名
if(empty($option['child_ids_field'])) $option['child_ids_field'] = 'childIds'; // 子ノードIDリストのフィールド名
if(empty($option['child_nodes_field'])) $option['child_nodes_field'] = 'childNodes'; // 子ノードリストのフィールド名
if(empty($option['bros_no_field'])) $option['bros_no_field'] = 'bros_no'; // 兄弟番号のフィールド名
if(empty($option['gene_no_field'])) $option['gene_no_field'] = 'gene_no'; // 世代のフィールド名
if(empty($option['offset_x_field'])) $option['offset_x_field'] = 'offset_x'; // オフセットXのフィールド名
if(empty($option['offset_y_field'])) $option['offset_y_field'] = 'offset_y'; // オフセットYのフィールド名
if(empty($option['html_tbl_class'])) $option['html_tbl_class'] = ''; // htmlテーブルのclass属性
if(empty($option['html_tbl_fields'])) $option['html_tbl_fields'] = 'id'; // htmlテーブルの表示フィールド
return $option;
}
/**
* 子ノードリストをセットする
*
* @note
* 2次元型のツリー構造データに対応。(親ID型)
*
* @param array $data 2次元ツリー構造データ 親IDを含むデータ
* @param array $option オプション
* - sort_field 子ノードをソートするフィールド。 空ならソートしない。(デフォ)
* - sort_order 子ノードのソート方向 0:昇順 (デフォ), 1:降順
* - id_field IDフィールド名
* - par_id_field 親IDフィールド名
* - child_ids_field 子ノードフィールド名
* @return array 子ノードリストをセットした2次元ツリー構造データ
*/
private function setChilds(&$data,$option=array('sort_field'=>'name')){
$sort_field = $option['sort_field']; // 子ノードソートフィールド
$sort_order = $option['sort_order']; // 子ノードソート向き
$id_field = $option['id_field']; // IDのフィールド名
$par_id_field = $option['par_id_field']; // 親IDのフィールド名
$child_ids_field = $option['child_ids_field']; // 子ノードIDリストのフィールド名
// ソートフラグ
$sort_flg = 0;
if(!empty($sort_field)) $sort_flg=1;
// 各ノードごとに子ノードのIDを検索し、子ノートリストとしてセットする。
$tmps=array();
foreach($data as &$ent){
$childs = array();
$par_id1 = $ent[$id_field];
foreach($data as &$ent2){
// 子ノードである場合
if($par_id1 == $ent2[$par_id_field]){
// ソートフラグがOFFなら、そのまま子ノードのIDを子ノードリストに追加する。
if($sort_flg==0){
$childs[] = $ent2[$id_field];
}
// ソートフラグがONなら、一時的データに格納する。
else{
$tmps[] = array(
$id_field => $ent2[$id_field],
$sort_field => $ent2[$sort_field],
);
}
}
}
unset($ent2);
// ソートフラグがONなら、一時的データをソートフィールドで並べ替えてからIDを子ノードリストにセットする。
if($sort_flg == 1){
$tmps = $this->sortData($tmps,$sort_field,$sort_order);
foreach($tmps as $tmpEnt){
$childs[] = $tmpEnt[$id_field];
}
$tmps=array(); // 一時的データをクリア
}
$ent[$child_ids_field] = $childs;
}
unset($ent);
return $data;
}
/**
* 特定のフィールドでデータを並べ替える
* @param array $data データ(2次元配列)
* @param string $sortField 並べ替えフィールド名
* @param int $orderFlg 0:昇順 , 1:降順
*/
private function sortData($data,$sortField,$orderFlg){
$sfList=array();// ソートフィールドリスト
foreach($data as $key=> $ent){
$sfList[$key]=$ent[$sortField];
}
$sortFlg = SORT_ASC;
if(!empty($orderFlg)){
$sortFlg = SORT_DESC;
}
array_multisort($sfList,$sortFlg,$data);
return $data;
}
/**
* キーid構造に変換する
*
* @param array $data エンティティ配列構造のデータ
* @param array $option
* - id_field IDフィールド名
* @return array キーid構造に変換したデータ
*/
private function convStructKeyId(&$data,$option){
$id_field = $option['id_field'];
$datax = array();
foreach($data as $ent){
$id = $ent[$id_field];
$datax[$id] = $ent;
}
return $datax;
}
/**
* 世代(オフセットX)、オフセットY、兄弟番号をセットする。
*
* @note
* 先にsetChilds関数で子ノードリストをデータにセットしておくこと。
*
* @param array $datax キーid構造データ
* @param array $option オプション
* @return array セット後のキーid構造データ
*/
private function setOffsetsX(&$datax,&$option){
$par_id_field = $option['par_id_field']; // 親IDフィールド
$id_field = $option['id_field']; // IDのフィールド名
// 創始ノードを探す
$start_id = -1;
foreach($datax as &$node){
if($node[$par_id_field] == 0){
$start_id = $node[$id_field];
break;
}
}
unset($node);
if($start_id == -1) return $datax;
// 再起呼び出し型関数: 世代番号付与関数(データ、id、世代番号、兄弟通番、&オフセットY,オプション)
$option['offset_y'] = 1;
$this->setOffsets($datax,$start_id,1,1,$option);
return $datax;
}
/**
* 世代番号付与関数
* @param array $datax キーid構造データ
* @param int $id
* @param int $gene_no 世代(オフセットX)
* @param int $bros_no 兄弟番号
* @param int $offset_y オフセットY
* @param array $option オプション
*/
private function setOffsets(&$datax,$id,$gene_no,$bros_no,&$option){
$id_field = $option['id_field']; // IDのフィールド名
$par_id_field = $option['par_id_field']; // 親IDのフィールド名
$child_ids_field = $option['child_ids_field']; // 子ノードIDリストのフィールド名
$child_nodes_field = $option['child_nodes_field']; // 子ノードリストのフィールド名
$bros_no_field = $option['bros_no_field']; // 兄弟番号のフィールド名
$gene_no_field = $option['gene_no_field']; // 世代のフィールド名
$offset_x_field = $option['offset_x_field']; // オフセットXのフィールド名
$offset_y_field = $option['offset_y_field']; // オフセットYのフィールド名
$offset_y = $option['offset_y']; // オフセットY
$node = &$datax[$id];
$node[$gene_no_field] = $gene_no; // 引数ノードに世代番号を付与する
$node[$bros_no_field] = $bros_no; // 引数ノードに兄弟通番を付与する
$node[$offset_x_field] = $gene_no; // 引数ノードにオフセットXを付与する
$node[$offset_y_field] = $offset_y; // 引数ノードにオフセットYを付与する
$childIds = $node[$child_ids_field]; // 引数ノードから子ノードリストを取得する
$option['prev_gene_no'] = $gene_no; // オフセットYの調整用
$gene_no ++;
$bros_no = 1;
foreach($childIds as $c_id){
if(!empty($datax[$c_id][$gene_no_field])) continue;
// 再起呼び出し 要素ノード、世代番号、兄弟通番、従妹通番、家系連番
$this->setOffsets($datax,$c_id,$gene_no,$bros_no,$option);
if($option['prev_gene_no'] == $gene_no){
$option['offset_y'] ++; // オフセットYをインプリメント
}
$bros_no++; // 兄弟通番をインプリメント
}
}
/**
* ツリー構造データに変換する
* @param array $datax キーID構造データ
* @param array $option オプション
* @return array ツリー構造データ
*/
private function convToStructTreeX($datax,$option){
$tree = array(); // ツリー構造データ
$par_id_field = $option['par_id_field']; // 親IDフィールド
$id_field = $option['id_field']; // IDのフィールド名
// 創始ノードを探す
$start_id = -1;
foreach($datax as $node){
if($node[$par_id_field] == 0){
$start_id = $node[$id_field];
break;
}
}
if($start_id == -1) return $datax;
$tree = $datax[$start_id]; // 創始ノードをツリー構造データの先頭にセット
$datax[$start_id]['set_flg'] = 1; // セットフラグ。無限ループ回避用
$option['counter'] = 0; // 無限ループ防止用のカウンター
// 再起呼び出し型関数: ツリー構造データに変換
$this->convToStructTree($tree,$datax,$start_id,$option);
return $tree;
}
/**
* 世代番号付与関数
* @param array tree ツリー構造データ
* @param array $datax キーid構造データ
* @param array $id
* @param array $option オプション
*/
private function convToStructTree(&$node,&$datax,$id,&$option){
// 無限ループ防止
$option['counter'] ++;
if($option['counter'] > 10000) return;
$child_ids_field = $option['child_ids_field']; // 子ノードIDリストのフィールド名
$child_nodes_field = $option['child_nodes_field']; // 子ノードリストのフィールド名
$childIds = $node[$child_ids_field]; // 引数ノードから子ノードリストを取得する
foreach($childIds as $c_id){
$cNode = $datax[$c_id];
if(empty($cNode['set_flg'])){
$datax[$c_id]['set_flg'] = 1;
unset($cNode['set_flg']);
// 再起呼び出し
$this->convToStructTree($cNode,$datax,$c_id,$option);
// 子ノードリストに追加
$node[$child_nodes_field][$c_id] = $cNode;
}
}
}
/**
* キーID構造からエンティティ配列構造に戻す
* @param array $datax キーID構造
* @return array エンティティ配列構造のデータ
*/
private function convToStructNormal(&$datax){
$data = array();
foreach($datax as $ent){
$data[] = $ent;
}
return $data;
}
/**
* 2次元マップに変換する
* @param array $datax
* @return array 2次元マップ
*/
private function convToMap(&$datax,$option){
if(empty($datax)) return array();
$offset_x_field = $option['offset_x_field']; // オフセットXのフィールド名
$offset_y_field = $option['offset_y_field']; // オフセットYのフィールド名
$cnt_x = 0;
$cnt_y = 0;
foreach($datax as &$ent){
if(isset($ent[$offset_x_field])){
$offset_x = $ent[$offset_x_field];
if($cnt_x < $offset_x) $cnt_x = $offset_x;
}
if(isset($ent[$offset_y_field])){
$offset_y = $ent[$offset_y_field];
if($cnt_y < $offset_y) $cnt_y = $offset_y;
}
}
unset($ent);
$map = array();
for($y=0;$y<$cnt_y;$y++){
$map[$y] = null;
for($x=0;$x<$cnt_x;$x++){
$map[$y][$x] = null;
}
}
foreach($datax as &$ent){
if(isset($ent[$offset_x_field]) && isset($ent[$offset_y_field])){
$x = $ent[$offset_x_field] - 1;
$y = $ent[$offset_y_field] - 1;
$map[$y][$x] = $ent;
}
}
return $map;
}
/**
* 2次元マップからHTMLテーブルのソースコードを作成
* @param array $map 2次元マップ
* @param array $option オプション
* - html_tbl_class htmlテーブルのclass属性 デフォ→空
* - html_tbl_fields htmlテーブルの表示フィールド(配列指定可能) デフォ→id
* @return string HTMLテーブルのソースコード
*/
private function createHtmlTbl(&$map,&$option){
$html_tbl_class = $option['html_tbl_class']; // HTMLテーブルのclass属性
$html_tbl_fields = $option['html_tbl_fields']; // htmlテーブルの表示フィールド
// htmlテーブルの表示フィールドが文字列なら配列化する
if(!is_array($html_tbl_fields)){
$html_tbl_fields = array($html_tbl_fields);
}
if(empty($map)) return '';
$cnt_y = count($map);
$cnt_x = count($map[0]);
$html = "<table class='tbl2'><tbody>";
for($y=0;$y<$cnt_y;$y++){
$html .= '<tr>';
for($x=0;$x<$cnt_x;$x++){
$td = "";
if(!empty($map[$y][$x])){
$ent = $map[$y][$x];
$td = '';
foreach($html_tbl_fields as $field){
$td .= $ent[$field] . '<br>';
}
}
$html .= "<td>{$td}</td>";
}
$html .= '</tr>';
}
$html .= '</tbody></table>';
return $html;
}
}
出力
2 生物
| 12 UMA
| 13 カッパ
| | | |
| 3 動物
| 5 無脊椎動物
| 10 ムカデ
| | |
| | 4 脊椎動物
| 6 哺乳類
| 1 ネコ
| |
| | | | 24 ブタ
| |
| | | | 14 ライオン
| |
| | | 9 昆虫
| 21 カブトムシ
| 22 タイワンカブト
|
| | | | | 23 ヘラクレス
|
| | | | 11 カマキリ
| |
| | | 7 爬虫類
| 15 イグアナ
| |
| | | 8 魚類
| 20 ガーラ
| |
| | | | 19 テラピア
| |
| 16 植物
| 18 キャベツ
| | | |
| | 17 ダイコン
| | | |
検証