Estoy buscando escribir una función que tome una matriz de páginas / categorías (de un resultado de base de datos plano) y genere una matriz de elementos de página / categoría anidados basados en los identificadores de los padres. Me gustaría hacer esto de forma recursiva, para poder realizar cualquier nivel de anidación.
Por ejemplo: estoy obteniendo todas las páginas en una consulta, y así es como se ve la tabla de la base de datos
+-------+---------------+---------------------------+
| id | parent_id | title |
+-------+---------------+---------------------------+
| 1 | 0 | Parent Page |
| 2 | 1 | Sub Page |
| 3 | 2 | Sub Sub Page |
| 4 | 0 | Another Parent Page |
+-------+---------------+---------------------------+
Y esta es la matriz con la que me gustaría terminar para procesar en mis archivos de vista:
Array
(
[0] => Array
(
[id] => 1
[parent_id] => 0
[title] => Parent Page
[children] => Array
(
[0] => Array
(
[id] => 2
[parent_id] => 1
[title] => Sub Page
[children] => Array
(
[0] => Array
(
[id] => 3
[parent_id] => 1
[title] => Sub Sub Page
)
)
)
)
)
[1] => Array
(
[id] => 4
[parent_id] => 0
[title] => Another Parent Page
)
)
He examinado y probado casi todas las soluciones con las que me he encontrado (hay muchas de ellas aquí en Stack Overflow, pero no he tenido suerte en conseguir algo lo suficientemente genérico que funcione tanto para páginas como para categorías.
Aquí está lo más cerca que he estado, pero no funciona porque estoy asignando a los niños al padre de primer nivel.
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);
}
Respuestas:
Una construcción de árboles genérica y muy simple:
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);
El algoritmo es bastante simple:
0
/ nada /null
/ lo que sea).parent_id
de un elemento coincide con la identificación principal actual que obtuvo en 1., el elemento es un elemento secundario del principal. Ponlo en tu lista de hijos actuales (aquí :)$branch
.children
elemento.En otras palabras, una ejecución de esta función devuelve una lista de elementos que son hijos de la identificación principal dada. Llámelo con
buildTree($myArray, 1)
, devolverá una lista de elementos que tienen la identificación principal 1. Inicialmente, esta función se llama con la identificación principal 0, por lo que se devuelven los elementos sin identificación principal, que son nodos raíz. La función se llama a sí misma de forma recursiva para encontrar hijos de hijos.fuente
$elements
matriz. Para arreglos pequeños, eso apenas importa, pero para conjuntos de datos grandes, querrá eliminar el elemento que ya coincide antes de pasarlo. Sin embargo, eso se vuelve algo complicado, así que lo dejé simple para que lo entiendas más fácilmente. :)array
es un indicio de tipo para$elements
, el primer argumento es simplearray $elements
.$rows
es una matriz de resultados de bases de datos como la de la pregunta.$children = buildTree(...)
parte y la función debería ser bastante obvia y simple.Sé que esta pregunta es antigua, pero estaba enfrentando un problema muy similar, excepto con una gran cantidad de datos. Después de un poco de lucha, logré construir el árbol en una pasada del conjunto de resultados, usando referencias. Este código no es bonito, pero funciona y funciona bastante rápido. No es recursivo, es decir, solo hay una pasada sobre el conjunto de resultados y luego una
array_filter
al final:$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);
Cuando se ejecuta en estos datos:
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 | +------+-------------+
El último
print_r
produce esta salida: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 ( ) ) ) ) )
Que es exactamente lo que estaba buscando.
fuente
Inspirándome en otras respuestas aquí, se me ocurrió mi propia versión para agrupar una matriz de matrices asociadas de forma recursiva (a cualquier profundidad arbitraria), utilizando la lista de funciones personalizadas para obtener claves de agrupación en cada nivel .
Aquí hay una versión simplificada de la variante original más compleja (con más parámetros para ajustar las perillas). Tenga en cuenta que emplea una función iterativa
groupByFn
simple como subrutina para realizar agrupaciones a niveles individuales./** * - 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' / '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 ); } }
Verifique la esencia para una colección más grande de funciones de utilidad de matriz junto con pruebas unitarias
fuente
Es posible usar php para obtener el resultado de mysql en una matriz y luego usarlo.
$categoryArr = Array(); while($categoryRow = mysql_fetch_array($category_query_result)){ $categoryArr[] = array('parentid'=>$categoryRow['parent_id'], 'id'=>$categoryRow['id']); }
fuente