クエリを変更すると、すべてのデータを一度に取得でき、それほど多くの計算を行う必要はありません (コードはテストされていません)。
/* Get all the data in one swoop and arrange it for easy mangling later */
function populate_data() {
$result = mysql_query('SELECT parent, COUNT(*) AS amount, GROUP_CONCAT(title) AS children FROM tree GROUP BY parent');
$data = array();
while ($row = mysql_fetch_assoc($result)) {
/* Each node has the amount of children and their names */
$data[$row['parent']] = array($row['children'], int($row['amount']));
}
return $data;
}
/* The function that does the whole work */
function get_children_per_level($data, $root) {
$current_children = array($root);
$next_children = array();
$ret = array();
while(!empty($current_children) && !empty($next_children)) {
$count = 0;
foreach ($current_children as $node) {
$count += $data[$node][0]; /* add the amount */
$next_children = array_merge($next_children, explode($data[$node][1])); /* and its children to the queue */
}
ret[] = $count;
$current_children = $next_children;
$next_children = array();
}
return $ret;
}
$data = populate_data();
get_children_per_level($data, 'Food');
関数を変更して、呼び出しごとに呼び出しを行うか、レベルごとに 1 つの呼び出しを行い、テーブル全体をメモリに読み込まずにデータ構造を設定することは難しくありません。すべてのデータを一度に取得して計算する方がはるかに効率的であるため、子が数個しかない深いツリーがある場合は、これに反対することをお勧めします。多くの子を持つ浅い木がある場合は、変更する価値があるかもしれません。
すべてを 1 つの関数にまとめることもできますが、必要のないときに繰り返し呼び出しを行うためにデータを再計算することは避けたいと思います。これに対する可能な解決策は、これをクラスにし、それを内部プライベート プロパティとして格納するコンストラクタとして関数を使用し、内部プライベートからデータを取得するため、最初のパラメータがない場合populate_data
と同じ単一のメソッドを使用することです。get_children_per_level
財産。
いずれにせよ、他の列ではなく ID 列を「親」参照として使用することをお勧めします。まず、名前のいずれかにコンマが含まれているとコードが壊れます:P. また、同じ名前の 2 つの異なる要素がある場合もあります。たとえば、あなたが持っている可能性がVegetables -> Red -> Pepper
ありRed
、果物のRed
.
もう 1 つの注意点は、DB データがツリーでない場合、コードが無限ループに入ることです。グラフにサイクルがある場合、それは決して終了しません。このバグは、$visited
既にアクセスしたすべてのノードを含む配列を保持し、それらを$next_children
ループ内の配列にプッシュしないことで簡単に解決できます (おそらくarray_diff($data[$node][1], $visited)
.