2011-12-21 11 views
63

Estoy buscando escribir una función que tome una matriz de páginas/categorías (de un resultado de base de datos plana) y genere una matriz de elementos de página/categoría anidados basados ​​en los identificadores principales . Me gustaría hacer esto recursivamente, para que se pueda hacer cualquier nivel de anidación.Función recursiva para generar matriz multidimensional a partir del resultado de la base de datos

Por ejemplo: Me retracto todas las páginas en una sola consulta, y esto es lo que la base de datos de la tabla parece

+-------+---------------+---------------------------+ 
| 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 me gustaría terminar con 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 buscado y probado casi todas las soluciones que he encontrado (hay un montón de ellos aquí en desbordamiento de pila, pero no han tenido suerte conseguir algo lo suficientemente genérico que funcione para ambos páginas y categorías.

Esto es lo más cerca que he estado, pero no funciona porque estoy asignando los hijos al primer nivel primario.

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); 
} 
+1

no puedes trabajar con una matriz de todas parent_ids y otra matriz de tus páginas? – djot

Respuesta

169

Algunos muy simple, la generación de árboles genérica:

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:

  1. Tome el conjunto de todos los elementos y la identificación de la matriz actual (inicialmente 0/nada/null/lo que sea).
  2. Pasa por todos los elementos.
  3. Si el parent_id de un elemento coincide con el ID padre actual que recibió en 1., el elemento es hijo del padre. Ponlo en tu lista de hijos actuales (aquí: $branch).
  4. Llamar recursivamente a la función con el id del elemento que acaba de identificar en 3., es decir, buscar todos los elementos secundarios de ese elemento y agregarlos como elemento children.
  5. Regrese su lista de niños encontrados.

En otras palabras, una ejecución de esta función devuelve una lista de elementos que son secundarios del ID principal dado. Llámelo con buildTree($myArray, 1), devolverá una lista de elementos que tienen el id principal 1. Inicialmente se llama a esta función con el id principal que es 0, de modo que se devuelven los elementos sin id padre, que son nodos raíz. La función se llama recursivamente para encontrar hijos de niños.

+0

Esto funciona perfectamente. Puedo ver dónde me equivoqué en la lógica de mi versión. ¡Manera de sacudirlo! No he necesitado usar recursiones muy a menudo en proyectos, así que esto ciertamente ayuda mucho. –

+1

Me alegro de que ayude. A tener en cuenta: esto es algo ineficiente, ya que siempre pasa toda la matriz '$ elements' hacia abajo. Para las matrices pequeñas que apenas importan, pero para los grandes conjuntos de datos, querrás eliminar el elemento ya emparejado antes de pasarlo. Sin embargo, eso se convierte en algo desordenado, así que lo dejé simple para su comprensión más fácil. :) – deceze

+5

@deceze Me gustaría ver la versión desordenada también. ¡Gracias por adelantado! –

0

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']); 
    } 
9

Sé que esta pregunta es antigua, pero me enfrentaba a un problema muy similar, excepto con una gran cantidad de datos. Después de algunas dificultades, logré construir el árbol en una sola pasada del conjunto de resultados, usando referencias. Este código no es bonito, pero funciona y funciona bastante rápido.Es no recursivo - es decir, sólo hay un pase al centro del conjunto de resultados y luego uno 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 | 
+------+-------------+ 

La última 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.

+0

Eres increíble. Esto funciona perfectamente para mi. Estaba buscando esto desde los últimos 2 días. Esta respuesta debe marcarse como la mejor respuesta. – Swadesh

+0

mientras que la solución es inteligente, pero este código tiene errores, me dio diferentes resultados en diferentes situaciones – Mohammadhzp

+0

@Mohammadhzp He estado usando esta solución en producción durante el último año y no tuve ningún problema con ella.Si sus datos son diferentes, obtendrá diferentes resultados :) –

0

Para matrices de gran tamaño:

pass array por referencia.

function buildTree(&$elements, $parentId = 0) { //pass main array by reference 
    $branch = array(); 

    foreach ($elements as $key => $element) { 
     if ($element['parent_id'] == $parentId) { 

      $element['children'] = buildTree($elements, $element['id']); 

      $branch[] = $element;     
     } 
    } 
    return $branch; 
} 

$tree = buildTree($rows); 

Si pasa matriz sea referencia a continuación misma matriz utilizará en cada función recursiva no hay necesidad de combinar las matrices entre padres e hijos en el extremo

Cuestiones relacionadas