
David Hanhill
Ich möchte eine Funktion schreiben, die ein Array von Seiten/Kategorien (aus einem flachen Datenbankergebnis) nimmt und ein Array von verschachtelten Seiten-/Kategorieelementen basierend auf den übergeordneten IDs generiert. Ich möchte dies rekursiv tun, damit jede Verschachtelungsebene durchgeführt werden kann.
Zum Beispiel: Ich rufe alle Seiten in einer Abfrage ab, und so sieht die Datenbanktabelle aus
+-------+---------------+---------------------------+
| id | parent_id | title |
+-------+---------------+---------------------------+
| 1 | 0 | Parent Page |
| 2 | 1 | Sub Page |
| 3 | 2 | Sub Sub Page |
| 4 | 0 | Another Parent Page |
+-------+---------------+---------------------------+
Und dies ist das Array, das ich am Ende in meinen Ansichtsdateien verarbeiten möchte:
Array
(
[0] => Array
(
[id] => 1
[parent_id] => 0
Recursive function to generate multidimensional array from database result => Parent Page
[children] => Array
(
[0] => Array
(
[id] => 2
[parent_id] => 1
Recursive function to generate multidimensional array from database result => Sub Page
[children] => Array
(
[0] => Array
(
[id] => 3
[parent_id] => 1
Recursive function to generate multidimensional array from database result => Sub Sub Page
)
)
)
)
)
[1] => Array
(
[id] => 4
[parent_id] => 0
Recursive function to generate multidimensional array from database result => Another Parent Page
)
)
Ich habe fast jede Lösung gesucht und ausprobiert, auf die ich gestoßen bin (es gibt viele davon hier bei Stack Overflow, aber ich hatte kein Glück, etwas generisches zu bekommen, das sowohl für Seiten als auch für Kategorien funktioniert.
Hier ist das nächste, was ich bekommen habe, aber es funktioniert nicht, weil ich die Kinder dem übergeordneten Element der ersten Ebene zuweise.
function page_walk($array, $parent_id = FALSE)
{
$organized_pages = array();
$children = array();
foreach($array as $index => $page)
{
if ( $page['parent_id'] == 0) // No, just spit it out and you're done
{
$organized_pages[$index] = $page;
}
else // If it does,
{
$organized_pages[$parent_id]['children'][$page['id']] = $this->page_walk($page, $parent_id);
}
}
return $organized_pages;
}
function page_list($array)
{
$fakepages = array();
$fakepages[0] = array('id' => 1, 'parent_id' => 0, 'title' => 'Parent Page');
$fakepages[1] = array('id' => 2, 'parent_id' => 1, 'title' => 'Sub Page');
$fakepages[2] = array('id' => 3, 'parent_id' => 2, 'title' => 'Sub Sub Page');
$fakepages[3] = array('id' => 4, 'parent_id' => 3, 'title' => 'Another Parent Page');
$pages = $this->page_walk($fakepages, 0);
print_r($pages);
}

verzeihen
Einige sehr einfache, generische Baumkonstruktionen:
function buildTree(array $elements, $parentId = 0) {
$branch = array();
foreach ($elements as $element) {
if ($element['parent_id'] == $parentId) {
$children = buildTree($elements, $element['id']);
if ($children) {
$element['children'] = $children;
}
$branch[] = $element;
}
}
return $branch;
}
$tree = buildTree($rows);
Der Algorithmus ist ziemlich einfach:
- Nehmen Sie das Array aller Elemente und die ID des aktuellen Elternteils (anfänglich
0
/nichts/null
/wie auch immer).
- Alle Elemente durchlaufen.
- Wenn die
parent_id
eines Elements mit der aktuellen übergeordneten ID übereinstimmt, die Sie in 1. erhalten haben, ist das Element ein untergeordnetes Element des übergeordneten Elements. Tragen Sie es in Ihre Liste der aktuellen Kinder ein (hier: $branch
).
- Rufen Sie die Funktion rekursiv mit der ID des Elements auf, das Sie gerade in 3. identifiziert haben, dh finden Sie alle untergeordneten Elemente dieses Elements und fügen Sie sie als hinzu
children
Element.
- Geben Sie Ihre Liste der gefundenen Kinder zurück.
Mit anderen Worten, eine Ausführung dieser Funktion gibt eine Liste von Elementen zurück, die Kinder der gegebenen Eltern-ID sind. Nennen Sie es mit buildTree($myArray, 1)
, wird eine Liste von Elementen zurückgegeben, die die Eltern-ID 1 haben. Anfangs wird diese Funktion mit der Eltern-ID 0 aufgerufen, sodass Elemente ohne Eltern-ID zurückgegeben werden, die Wurzelknoten sind. Die Funktion ruft sich selbst rekursiv auf, um Kinder von Kindern zu finden.
Ich weiß, dass diese Frage alt ist, aber ich stand vor einem sehr ähnlichen Problem – außer mit einer sehr großen Datenmenge. Nach einigem Kampf gelang es mir, den Baum in einem Durchgang der Ergebnismenge zu erstellen – unter Verwendung von Referenzen. Dieser Code ist nicht schön, aber er funktioniert und er funktioniert ziemlich schnell. Es ist nicht rekursiv – das heißt, es gibt nur einen Durchgang über die Ergebnismenge und dann einen array_filter
Am Ende:
$dbh = new PDO(CONNECT_STRING, USERNAME, PASSWORD);
$dbs = $dbh->query("SELECT n_id, n_parent_id from test_table order by n_parent_id, n_id");
$elems = array();
while(($row = $dbs->fetch(PDO::FETCH_ASSOC)) !== FALSE) {
$row['children'] = array();
$vn = "row" . $row['n_id'];
${$vn} = $row;
if(!is_null($row['n_parent_id'])) {
$vp = "parent" . $row['n_parent_id'];
if(isset($data[$row['n_parent_id']])) {
${$vp} = $data[$row['n_parent_id']];
}
else {
${$vp} = array('n_id' => $row['n_parent_id'], 'n_parent_id' => null, 'children' => array());
$data[$row['n_parent_id']] = &${$vp};
}
${$vp}['children'][] = &${$vn};
$data[$row['n_parent_id']] = ${$vp};
}
$data[$row['n_id']] = &${$vn};
}
$dbs->closeCursor();
$result = array_filter($data, function($elem) { return is_null($elem['n_parent_id']); });
print_r($result);
Bei Ausführung auf diesen Daten:
mysql> select * from test_table;
+------+-------------+
| n_id | n_parent_id |
+------+-------------+
| 1 | NULL |
| 2 | NULL |
| 3 | 1 |
| 4 | 1 |
| 5 | 2 |
| 6 | 2 |
| 7 | 5 |
| 8 | 5 |
+------+-------------+
Das Letzte print_r
erzeugt diese Ausgabe:
Array
(
[1] => Array
(
[n_id] => 1
[n_parent_id] =>
[children] => Array
(
[3] => Array
(
[n_id] => 3
[n_parent_id] => 1
[children] => Array
(
)
)
[4] => Array
(
[n_id] => 4
[n_parent_id] => 1
[children] => Array
(
)
)
)
)
[2] => Array
(
[n_id] => 2
[n_parent_id] =>
[children] => Array
(
[5] => Array
(
[n_id] => 5
[n_parent_id] => 2
[children] => Array
(
[7] => Array
(
[n_id] => 7
[n_parent_id] => 5
[children] => Array
(
)
)
[8] => Array
(
[n_id] => 8
[n_parent_id] => 5
[children] => Array
(
)
)
)
)
[6] => Array
(
[n_id] => 6
[n_parent_id] => 2
[children] => Array
(
)
)
)
)
)
Das ist genau das, wonach ich gesucht habe.
public function testTree(){
$array = [
['id'=>7,'parent_id'=>3],
['id'=>1,'parent_id'=>0],
['id'=>2,'parent_id'=>0],
['id'=>3,'parent_id'=>1],
['id'=>4,'parent_id'=>1],
['id'=>5,'parent_id'=>2],
['id'=>6,'parent_id'=>1],
['id'=>8,'parent_id'=>4],
['id'=>9,'parent_id'=>4],
['id'=>10,'parent_id'=>0]
];
$res = $this->buildTree($array);
print_r($res);
}
public function buildTree($array,$id_key = 'id',$parent_key = 'parent_id'){
$res = [];
foreach($array as $y){
$array_with_id[$y[$id_key]] = $y;
}
foreach($array_with_id as $key => $element){
if($element[$parent_key]){
$array_with_id[$element[$parent_key]]['childrens'][$key] = &$array_with_id[$key];
}else{
$res[$element[$id_key]] = &$array_with_id[$key];
}
}
return $res;
}
Zu viele Operationen sind rekursiv vorgesehen, ich denke, es ist der beste Weg.
Inspiriert von anderen Antworten hier, habe ich mir eine eigene Version zum Gruppieren von an ausgedacht Array von zugeordneten Arrays rekursiv (bis zu einer beliebigen Tiefe), indem Sie Liste der benutzerdefinierten Funktionen zu erhalten Schlüssel gruppieren auf jeder Ebene.
Hier ist eine vereinfachte Version der ursprüngliche komplexere Variante (mit mehr Parametern zum Anpassen von Knöpfen). Beachten Sie, dass es eine einfache verwendet Iterative Funktion groupByFn
als Subroutine zum Durchführen der Gruppierung auf einzelnen Ebenen.
/**
* - Groups a (non-associative) array items recursively, essentially converting it into a nested
* tree or JSON like structure. Inspiration taken from: https://stackoverflow.com/a/8587437/3679900
* OR
* - Converts an (non-associative) array of items into a multi-dimensional array by using series
* of callables $key_retrievers and recursion
*
* - This function is an extension to above 'groupByFn', which also groups array but only till 1 (depth) level
* (whereas this one does it till any number of depth levels by using recursion)
* - Check unit-tests to understand further
* @param array $data Array[mixed] (non-associative) array of items that has to be grouped / converted to
* multi-dimensional array
* @param array $key_retrievers Array[Callable[[mixed], int|string]]
* - A list of functions applied to item one-by-one, to determine which
* (key) bucket an item goes into at different levels
* OR
* - A list of callables each of which takes an item or input array as input and returns an int
* or string which is to be used as a (grouping) key for generating multi-dimensional array.
* @return array A nested assoc-array / multi-dimensional array generated by 'grouping' items of
* input $data array at different levels by application of $key_retrievers on them (one-by-one)
*/
public static function groupByFnRecursive(
array $data,
array $key_retrievers
): array {
// in following expression we are checking for array-length = 0 (and not nullability)
// why empty is better than count($arr) == 0 https://stackoverflow.com/a/2216159/3679900
if (empty($data)) {
// edge-case: if the input $data array is empty, return it unmodified (no need to check for other args)
return $data;
// in following expression we are checking for array-length = 0 (and not nullability)
// why empty is better than count($arr) == 0 https://stackoverflow.com/a/2216159/3679900
} elseif (empty($key_retrievers)) {
// base-case of recursion: when all 'grouping"https://stackoverflow.com/"nesting' into multi-dimensional array has been done,
return $data;
} else {
// group the array by 1st key_retriever
$grouped_data = self::groupByFn($data, $key_retrievers[0]);
// remove 1st key_retriever from list
array_shift($key_retrievers);
// and then recurse into further levels
// note that here we are able to use array_map (and need not use array_walk) because array_map can preserve
// keys as told here:
// https://www.php.net/manual/en/function.array-map.php#refsect1-function.array-map-returnvalues
return array_map(
static function (array $item) use ($key_retrievers): array {
return self::groupByFnRecursive($item, $key_retrievers);
},
$grouped_data
);
}
}
Überprüfen Sie das Wesentliche für größer Sammlung von Array-Hilfsfunktionen zusammen mit Unit-Tests
Es ist möglich, PHP zu verwenden, um das MySQL-Ergebnis in ein Array zu bekommen und es dann zu verwenden.
$categoryArr = Array();
while($categoryRow = mysql_fetch_array($category_query_result)){
$categoryArr[] = array('parentid'=>$categoryRow['parent_id'],
'id'=>$categoryRow['id']);
}
9869600cookie-checkRekursive Funktion zum Generieren eines mehrdimensionalen Arrays aus dem Datenbankergebnisyes
Können Sie nicht einfach mit einem Array aller parent_ids und einem weiteren Array für Ihre Seiten arbeiten?
– djot
21. Dezember 2011 um 9:17 Uhr