【PHP】
共通キーのバリューを合併する関数を自作した

投稿日:2018/04/05

まずはPHPへの愚痴

PHPの"リスト"といえば連想配列でもありますね。
そこでリストの要素の合併・追加というものがあるんですが…。
ネットで調べても多くは"キーが異なる要素を含むリストの合併"ばかりです。
以下のように書けますね。

【ソース1】異なるキーの要素を持つリストの合併
<?php $array1=array("a"=>array(1,2,3)); $array2=array("b"=>array(4,5,6)); $array1+=$array2; /* 以下のように書いても同様の処理になる $array1=array_merge($array1,$array2); */ print_r($array1); ?>
Array ( [a] => Array ( [0] => 1 [1] => 2 [2] => 3 ) [b] => Array ( [0] => 4 [1] => 5 [2] => 6 ) )


ではここからが本題の愚痴です。
" 同じキーだったらそのバリューはそのキーの下に合併できないの?"
これですよ!以下のソースを見てください。
$array2のキーを「a」に変更しています。

【ソース2】同じキーを持つリストの合併
<?php $array1=array("a"=>array(1,2,3)); $array2=array("a"=>array(4,5,6)); $array1+=$array2; print_r($array1); echo "=================================\n"; $array1=array_merge($array1,$array2); print_r($array1); ?>
Array ( [a] => Array ( [0] => 1 [1] => 2 [2] => 3 ) ) ================================= Array ( [a] => Array ( [0] => 4 [1] => 5 [2] => 6 ) )

おかしいと思いません?
前者の書き方に至っては全然追加されていないじゃないですか。後者の書き方に至っては引数の順序にもよるんですが、どちらか一方しか追加されていないじゃないですか。
これ知った時ショックでしたよ。

なので自作した。

【ソース3】MergeChildInAsocArray関数
<?php function MergeChildInAsocArray($parent,$child){ //childにしかないKeyを取得 $NotpKeys=array_diff_key($child,$parent); //共通Keyをリストとして合併 foreach($child as $cKey=>$cValue){ foreach($parent as $pKey=>&$pValue){ if($cKey===$pKey){ if((is_array($cValue))&&(is_array($pValue))){ //valueが配列かどうか。配列でなかったら配列にして合併。 $pValue=array_merge($pValue,$cValue); }else{ $pValue=(is_array($pValue)) ? $pValue:array($pValue); $cValue=(is_array($cValue)) ? $cValue:array($cValue); $pValue=array_merge($pValue,$cValue); } } } } //childにあってparentにないペアをparentに追加 if(count($NotpKeys)>0){ $parent+=$NotpKeys; } /* ここにあたっては三項演算子で書いた方が処理が速かった。 $parent=(count($NotpKeys)>0) ? $parent+$NotpKeys:$parent; */ return $parent; } ?>

【引数】
●parent:合併元・合併先。
こちらが保持するキー(=parent)に照準を当てて処理させています。
●child:合併対象。
こちらが保持するバリュー(=child)を合併元の共通のキー(=parent)に合併します。
実のところ、上の処理だけではなく異なるキーの合併も実現しちゃっていますがw
【戻り値】
●parent:合併が完了したおNEWなparentを返します。

名付けが悪いとかparentとchildのイメージがつかないとかはさておいてくださいね(-_-;)
本人としてはparent=キー、child=バリューです。
わりと汎用的なリストの合併処理ができるように凝らしてみました。
以下のことが可能です。
①(通常の)異なるキーを持つリストの合併

【ソース4】
<?php /*バリューが単体*/ $arr1=array("a"=>1); $arr2=array("b"=>2); print_r(MergeChildInAsocArray($arr1,$arr2)); echo "==============================\n"; /*バリューがリスト*/ $arr3=array("c"=>array(5,6)); $arr4=array("d"=>array(7,8)); print_r(MergeChildInAsocArray($arr3,$arr4)); ?>
Array ( [a] => 1 [b] => 2 ) ============================== Array ( [c] => Array ( [0] => 5 [1] => 6 ) [d] => Array ( [0] => 7 [1] => 8 ) )

普通に合併できていますね。

②共通キーを持つバリューの合併

【ソース5】
<?php /*バリューが単体*/ $arr1=array("a"=>1); $arr2=array("a"=>2); print_r(MergeChildInAsocArray($arr1,$arr2)); echo "==============================\n"; /*バリューがリスト*/ $arr3=array("b"=>array(0,1)); $arr4=array("b"=>array(2,3)); print_r(MergeChildInAsocArray($arr3,$arr4)); ?>
Array ( [a] => Array ( [0] => 1 [1] => 2 ) ) ============================== Array ( [b] => Array ( [0] => 0 [1] => 1 [2] => 2 [3] => 3 ) )

ここが本題!これは感動モノですよ!←
共通キーのバリューがちゃんと共通キーの下に合併されています!
バリューが単体であってもリストであってもお構いなしです!単体の場合はリストにしています。

③さらに深い次元での合併を簡単に

【ソース6】
<?php $arr1=array("a"=>array("b"=>2,3,"c"=>4,"d"=>array(5,6)),"z"=>3,"x"=>array(21,52)); $arr2=array("a"=>array(7,"b"=>array(2,3),"d"=>array(8,9))); //"親"の残りの要素との合併でMergeChildInAsocArray関数を使用した場合 print_r(MergeChildInAsocArray($arr1, array("a"=>MergeChildInAsocArray($arr1["a"],$arr2["a"])))); echo "======================================================================/n"; //"親"の残りの要素との合併でarray_merge関数を使用した場合 print_r(array_merge($arr1,array("a"=>MergeChildInAsocArray($arr1["a"],$arr2["a"])))); ?>
Array ( [a] => Array ( [b] => Array ( [0] => 2 [1] => 2 [2] => 3 ) [0] => 3 [c] => 4 [d] => Array ( [0] => 5 [1] => 6 [2] => 8 [3] => 9 ) [1] => Array ( [0] => 3 [1] => 7 ) ) [z] => 3 [x] => Array ( [0] => 21 [1] => 52 ) ) ======================================================================/nArray ( [a] => Array ( [b] => Array ( [0] => 2 [1] => 2 [2] => 3 ) [0] => Array ( [0] => 3 [1] => 7 ) [c] => 4 [d] => Array ( [0] => 5 [1] => 6 [2] => 8 [3] => 9 ) ) [z] => 3 [x] => Array ( [0] => 21 [1] => 52 ) )

リストとしてのバリューが持つキーのバリュー(要は"孫")の合併も、"子"のキーを引数にすることで実現することができます。
しかしこれだけでは結果はその"子"のキー以下のリストしか返ってきませんので、合併に関与していない"親"のキーとその他の"親"のバリューを併せるために最後にもう1回合併を行っています。
ただし(詳しくは後述しますが)問題点がありますので、ここでは注意が必要です。そうした理由による比較のため、ソース6では最後のもう1度の合併においてMergeChildInAsocArray関数を用いる場合とarray_merge関数を用いる場合の2通りを用意しています。

④複雑なリストの合併(①②③のごちゃまぜ)

【ソース7】
<?php $arr1=array("a"=>array("b"=>array(4,5,6)),"c"=>5,"d"=>array(10,11)); $arr2=array("a"=>array("b"=>array(7,8,9)),"d"=>array(2,3),"e"=>array(12,13,14)); print_r($arr1); print_r($arr2); echo "=================================\n"; print_r(MergeChildInAsocArray($arr2, MergeChildInAsocArray($arr1, array("a"=>MergeChildInAsocArray($arr1["a"],$arr2["a"]))))); /* 処理の順としては、 "キーdの合併・キーeの追加" "キーcの追加" "キーaの追加・キーaのバリューにあるキーbの合併" */ ?>
Array ( [a] => Array ( [b] => Array ( [0] => 4 [1] => 5 [2] => 6 ) ) [c] => 5 [d] => Array ( [0] => 10 [1] => 11 ) ) Array ( [a] => Array ( [b] => Array ( [0] => 7 [1] => 8 [2] => 9 ) ) [d] => Array ( [0] => 2 [1] => 3 ) [e] => Array ( [0] => 12 [1] => 13 [2] => 14 ) ) ================================= Array ( [a] => Array ( [b] => Array ( [0] => 4 [1] => 5 [2] => 6 [3] => 7 [4] => 8 [5] => 9 ) ) [d] => Array ( [0] => 2 [1] => 3 [2] => 10 [3] => 11 ) [e] => Array ( [0] => 12 [1] => 13 [2] => 14 ) [c] => 5 )

MergeChildInAsocArray関数を使うあたりで改行させていますが、この場合3回使って"うまく"実装できました。

問題点

先述した「③さらに深い次元での合併を簡単に」のソース6を見て気づいた方もいらっしゃるのではないでしょうか。
ここでいくつかの問題点があったのです。

●単体とリストが混在するバリューに対応し辛い
もし 共通キーのあるバリューがリスト形式であり、かつ単体とリストが混在していた状態であった時にMergeChildInAsocArray関数を使用すると、
①単体のキーが「0」であると認識されてまとめられてしまい、
②キーの合併において単体だったものが上の統一されたものとは別に重複して追加されてしまう
というややこしい結果のリストが返されてしまいます。
その例としてソース6の結果の後者で①が、前者で①と②の両方が発生していることが見受けられます。

ただし、そもそも配列と連想配列が混在したリストなんて作る人はそういないと思いますが、 極力どちらかで純粋に使用した方が良いです。
そういう意味ではソース7は純粋な連想配列である2つのリストを合併していることでうまくいっている例ではありますね。

●バリューの重複を許してしまう
例としてソース6の(いずれの)結果のキーb以下を見てもらうと分かると思いますが、重複するバリュー(例では「2」)が複数追加されてしまっています。

実は既存の関数で実装はできた

実を言いますと、既存の関数であるarray_merge関数のみを繰り返し使用することで同じ処理を実現することが可能であることが分かりました。
"子"(=バリュー)までの簡単な合併なら以下のように書けます。

【ソース8】
<?php $arr1=array("a"=>array(1,2),"b"=>2); $arr2=array("a"=>array(3,4),"c"=>4); print_r(array_merge($arr2, array_merge($arr1, array("a"=>array_merge($arr1["a"],$arr2["a"]))))); /* 処理の順としては、 "キーcの追加" "キーbの追加" "キーaの追加・キーaのバリューの合併" */ ?>
Array ( [a] => Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 ) [c] => 4 [b] => 2 )

案外できたもんですねぇ…。MergeChildInAsocArray関数と比べれば複数回呼び出す必要が出てきますが。

続いてソース7と同じ処理を実現します。

【ソース9】
<?php print_r(array_merge($arr2, array_merge($arr1, array_merge( array("a"=>array_merge($arr1["a"], array("b"=>array_merge($arr1["a"]["b"],$arr2["a"]["b"])))), array("d"=>array_merge($arr1["d"],$arr2["d"])))))); /* 処理の順としては、 "キーeの追加" "キーcの追加" "バリューの合併を行ったキー(aとd)の合併" "キーaの追加・キーaのバリューの合併" "キーbの追加・キーbのバリューの合併" "キーdのバリューの合併" */ ?>
Array ( [a] => Array ( [b] => Array ( [0] => 4 [1] => 5 [2] => 6 [3] => 7 [4] => 8 [5] => 9 ) ) [d] => Array ( [0] => 10 [1] => 11 [2] => 2 [3] => 3 ) [e] => Array ( [0] => 12 [1] => 13 [2] => 14 ) [c] => 5 )

やはりソース8と同様、MergeChildInAsocArray関数と同じ処理を実現するのにarray_merge関数を2倍以上呼び出さないといけないのはありますね。
更に、 多次元のキーを指定しての引数の設定というのはややこしくなるばかりです。

しかし処理速度はというと…ソース7とソース9と(、ソース7と同じではあるが、MergeChildInAsocArray関数においてソース3でコメントアウトしている三項演算子を採用したものと)で計測比較してみました。

【処理速度の比較】
自作1(if文):0.00051116943359375 自作2(三項演算子):0.00045490264892578 既存の関数使用:0.00044584274291992

あぅ、負けとる。辛い…。
可読性で行数が減っているだけ勝っていると思いたい…。

最後に

PackagistだのComposerだのGitHubだの使って自作関数(正しくはクラス)をライブラリとして公開して、他者がインストールして使用できるようにしようかと思ったけど、調べたら面倒そうだったのでこちらに載せるだけにしました(´・ω・`)
(一応GitHub↓には載せましたが)
github-KagenoMoheji/MoheFuncs-PHP
ソース3の関数をコピペしてどうぞご活用くださいませ。

タグ:

Comment

コメントはありません。
There's no comment.