再帰的に関数を適用したときの動きがよくわかりません。
確かに再帰的な適用は頭で理解しにくいです。実際に例を用いて視覚的に考えると見えてきます。
丁寧に見ていきましょう!
今回実現したいこと
各部署の正式名を表示させることが目的です。
以下の3部署がデータベースに登録されています。
- 総務部
- 人事課
- 福利厚生係
それぞれ、人事課は総務部を親に持ち、福利厚生係は人事部を親に持つとします。
つまり、各部署の正式名は次のようになります。
- 総務部
- 総務部 人事課
- 総務部 人事課 福利厚生係
この状態を目指して、再帰的に関数を実行して意図する値を取得します。
大きな流れは次のとおりです。
- データを利用しやすい形に加工する
- 再帰的に関数を適用する
データを加工する
元データ
$original_departments = [ ["id" => 1, "name" => "総務部", "parent_id" => null], ["id" => 2, "name" => "人事課", "parent_id" => 1], ["id" => 3, "name" => "福利厚生係", "parent_id" => 2], ];
parent_idで親の部署を特定できる構成になっています。人事課の場合は、parent_idが1のため、idが1である総務部を親に持ちます。
データを加工して、idを配列のキーとする値に変換します。
$departments = []; foreach ($original_departments as $original_department) { $departments[$original_department['id']] = [ "id" => $original_department['id'], "name" => $original_department['name'], "parent_id" => $original_department['parent_id'], ]; } //各idをキーに持つ$departmentsの配列ができました。 array:3 [▼ 1 => array:4 [▼ "id" => 1 "name" => "総務部" "parent_id" => null ] 2 => array:4 [▼ "id" => 2 "name" => "人事課" "parent_id" => 1 ] 3 => array:4 [▼ "id" => 3 "name" => "福利厚生係" "parent_id" => 2 ] ]
再帰的に関数を適用する
public function setParentName($departments, &$department, $parent) { if ($parent['parent_id']) { $parent_department = $departments[$parent['parent_id']]; $department['name'] = $parent_department["name"] . " " . $department['name']; $this->setParentName($departments, $department, $parent_department); } return $department['name']; }
foreach ($departments as $key => $department) { $departments[$key]['new_name'] = $this->setParentName($departments, $department, $department); } //実行結果 array:3 [▼ 1 => array:5 [▼ "id" => 1 "name" => "総務部" "parent_id" => null "new_name" => "総務部" ] 2 => array:5 [▼ "id" => 2 "name" => "人事課" "parent_id" => 1 "new_name" => "総務部 人事課" ] 3 => array:5 [▼ "id" => 3 "name" => "福利厚生係" "parent_id" => 2 "new_name" => "総務部 人事課 福利厚生係" ] ]
new_nameに正式名が表示されています。なぜこのような形になるのか解説します。
ループ1周目
$departmentsをループさせた1週目を考えます。
1 => array:4 [▼ "id" => 1 "name" => "総務部" "parent_id" => null ]
1周目はこの行が適用されるため、parent_idはnullになります。そのため、if文には入らず、$department[‘name’]である総務部が返されるため、$departments[1][‘new_name’]は総務部になります。
public function setParentName($departments, &$department, $parent) { if ($parent['parent_id']) { $parent_department = $departments[$parent['parent_id']]; $department['name'] = $parent_department["name"] . " " . $department['name']; $this->setParentName($departments, $department, $parent_department); } return $department['name']; }
ループ2行目
次に2行目の場合を考えます。
2 => array:4 [▼ "id" => 2 "name" => "人事課" "parent_id" => 1 ]
parent_idがあるため、if文内に入ります。
public function setParentName($departments, &$department, $parent) { if ($parent['parent_id']) { $parent_department = $departments[$parent['parent_id']];
$parent_departmentは親の部署を表しています。データの加工をしたときに、idをキーに持つ配列に変換しているため、$departments[$parent[‘parent_id’]]とすることで、下記の親の部署の行を取得しています。
1 => array:5 [▼ "id" => 1 "name" => "総務部" "parent_id" => null "new_name" => "総務部" ]
次に$department[‘name’]に親の元々の部署名である「総務部」と2行目の部署名である「人事課」を結合しています。
$department['name'] = $parent_department["name"] . " " . $department['name'];
この処理をparent_idがなくなるまで実行して、一番上の階層までたどり着きたいため、ここで再帰的に関数を実行します。
$this->setParentName($departments, $department, $parent_department);
このときの$departmentはどうなっているでしょうか?少し考えてみてください。
nameに「総務部と人事課」が入った状態です。先程の変更が適用されています。
2 => array:4 [▼ "id" => 2 "name" => "総務部 人事課" "parent_id" => 1 ]
それでは、$parent_departmentはどうなるでしょうか。人事課の親である総務部ですね。
1 => array:4 [▼ "id" => 1 "name" => "総務部" "parent_id" => null ]
この状態でsetParentNameメソッドが実行されます。
$parentで渡ってくる総務部のparent_idはnullのため、この時引数でわたされた$departmentのnameが戻り値として返されます。
つまり、「総務部 人事課」ですね!
//2回目 public function setParentName($departments, &$department, $parent) { if ($parent['parent_id']) { $parent_department = $departments[$parent['parent_id']]; $department['name'] = $parent_department["name"] . " " . $department['name']; $this->setParentName($departments, $department, $parent_department); } return $department['name']; //総務部 人事課 }
//1回目 public function setParentName($departments, &$department, $parent) { if ($parent['parent_id']) { $parent_department = $departments[$parent['parent_id']]; $department['name'] = $parent_department["name"] . " " . $department['name']; $this->setParentName($departments, $department, $parent_department); //2回目の変更が反映 } return $department['name']; //総務部 人事課 }
これで最終的に$departments[2][‘new_name’]は総務部 人事課になります。
ループ3周目
最後に3周目を見ていきましょう!後少し!!!
3 => array:4 [▼ "id" => 3 "name" => "福利厚生係" "parent_id" => 2 ]
2周目と同じくif文内に入ります。今回はparent_idが2になります。このときの$parent_departmentはidが2の人事課になりますね。$department[‘name’]は親の部署名の人事課 + 3行目の部署名である福利厚生係です。
public function setParentName($departments, &$department, $parent) { if ($parent['parent_id']) { $parent_department = $departments[$parent['parent_id']]; $department['name'] = $parent_department["name"] . " " . $department['name']; $this->setParentName($departments, $department, $parent_department); } return $department['name']; }
このときの$departmentは次の形です。復習ですね
3 => array:4 [▼ "id" => 3 "name" => "人事課 福利厚生係" "parent_id" => 2 ]
$parent_departmentは次の形です。
2 => array:4 [▼ "id" => 2 "name" => "人事課" "parent_id" => 1 ]
これらを引数にsetParentNameメソッドを再帰的に実行します。
今度の$parentのparent_idは1になります。そのため、再度if文の分岐に入ります。
public function setParentName($departments, &$department, $parent) { if ($parent['parent_id']) { $parent_department = $departments[$parent['parent_id']]; $department['name'] = $parent_department["name"] . " " . $department['name']; $this->setParentName($departments, $department, $parent_department); } return $department['name']; }
このとき、$parent_departmenは$departments[1]になるため、総務部になります。
1 => array:4 [▼ "id" => 1 "name" => "総務部" "parent_id" => null ]
$department[‘name’]は「総務部 + $department[‘name’]」です。$department[‘name’]は「人事課 福利厚生係」なので、最終的には「総務部 人事課 福利厚生係」になります。
そして、setParentNameメソッドで再帰的に実行します。
このときの$departmentはもう説明不要ですね。
3 => array:4 [▼ "id" => 3 "name" => "総務部 人事課 福利厚生係" "parent_id" => 2 ]
$parent_departmentは総務部です。
1 => array:4 [▼ "id" => 1 "name" => "総務部" "parent_id" => null ]
これらを引数にsetParentNameメソッドを実行します。
public function setParentName($departments, &$department, $parent) { if ($parent['parent_id']) { $parent_department = $departments[$parent['parent_id']]; $department['name'] = $parent_department["name"] . " " . $department['name']; $this->setParentName($departments, $department, $parent_department); } return $department['name']; }
総務部のparent_idはnullなので、if文には入らず$department[‘name’]が戻り値になります。$department[‘name’]は「総務部 人事課 福利厚生係」です。
//3回目 public function setParentName($departments, &$department, $parent) { if ($parent['parent_id']) { $parent_department = $departments[$parent['parent_id']]; $department['name'] = $parent_department["name"] . " " . $department['name']; $this->setParentName($departments, $department, $parent_department); } return $department['name']; //総務部 人事課 福利厚生係 }
//2回目
public function setParentName($departments, &$department, $parent) {
if ($parent['parent_id']) {
$parent_department = $departments[$parent['parent_id']];
$department['name'] = $parent_department["name"] . " " . $department['name'];
$this->setParentName($departments, $department, $parent_department); //3回目変更が$departmentに反映
}
return $department['name']; //総務部 人事課 福利厚生係
}
//1回目 public function setParentName($departments, &$department, $parent) { if ($parent['parent_id']) { $parent_department = $departments[$parent['parent_id']]; $department['name'] = $parent_department["name"] . " " . $department['name']; $this->setParentName($departments, $department, $parent_department); //3回目変更が$departmentに反映 } return $department['name']; //総務部 人事課 福利厚生係 }
このように値が登ってきて、最終的に$departments[3][‘new_name’]に「総務部 人事課 福利厚生係」が挿入されることになります。
なぜ参照渡しをしているのか
setParentNameメソッドの$departmentが参照渡しになっていると気づいた方もいるかと思います。その理由は、再帰的に実行した結果を$depatrmentの変数に反映させたいからです。
public function setParentName($departments, &$department, $parent)
値渡しと参照渡しがわからない場合はこちらがおすすめです!
値渡しの場合のリターン前にdumpを実行した結果を見てみます。わかりやすくするために、福利厚生係の子に新たな部署としてtackチームを設定しています。
public function setParentName($departments, $department, $parent) { if ($parent['parent_id']) { $parent_department = $departments[$parent['parent_id']]; $department['name'] = $parent_department["name"] . " " . $department['name']; $this->setParentName($departments, $department, $parent_department); } dump($department['name']); return $department['name']; } //実行結果 "総務部" "総務部 人事課" "総務部 人事課" "総務部 人事課 福利厚生係" "総務部 人事課 福利厚生係" "人事課 福利厚生係" "総務部 人事課 福利厚生係 tackチーム" "総務部 人事課 福利厚生係 tackチーム" "人事課 福利厚生係 tackチーム" "福利厚生係 tackチーム" (参考:参照渡しの場合) "総務部" "総務部 人事課" "総務部 人事課" "総務部 人事課 福利厚生係" "総務部 人事課 福利厚生係" "総務部 人事課 福利厚生係" "総務部 人事課 福利厚生係 tackチーム" "総務部 人事課 福利厚生係 tackチーム" "総務部 人事課 福利厚生係 tackチーム" "総務部 人事課 福利厚生係 tackチーム"
最終的な戻り値が、総務部 人事課 福利厚生係は「人事課 福利厚生係」になり、総務部 人事課 福利厚生係 tackチームは「福利厚生係 tackチーム」になっているのがわかります。
これは、setParentNameメソッド内で変更した$departmentの内容が元の$departmentには影響を与えないためです。
$department['name'] = $parent_department["name"] . " " . $department['name'];
この部分で変更した値が戻り値として返されるため、再帰処理に入る前の、最初に変更した「人事課 福利厚生係」「福利厚生係 tackチーム」の部分が最終的な値として返されることになります。
お疲れさまでした!再帰的な適用は値がどこでどんな形になっているのか理解しにくいですね。一つ一つ丁寧に見ていくことが結局近道な気がします!
コメント