web-dev-qa-db-fra.com

Construisez un arbre à partir d'un tableau plat dans PHP

J'ai regardé sur Internet et je n'ai pas tout à fait trouvé ce que je cherchais. J'ai un tableau plat avec chaque élément contenant un "id" et un "parent_id". Chaque élément n'aura qu'un seul parent, mais peut avoir plusieurs enfants. Si parent_id = 0, il est considéré comme un élément de niveau racine. J'essaie de mettre mon tableau plat dans un arbre. Les autres exemples que j'ai trouvés ne copient que l'élément vers le parent, mais l'original existe toujours.

MODIFIER

Chaque élément du tableau de départ est lu à partir d'un fichier XML distinct. Le fichier lui-même aura "0" comme valeur pour parent_id s'il n'a pas de parent. Les clés sont en fait des chaînes.

Je suis désolé pour la confusion plus tôt. Espérons que cela soit plus clair:

/ MODIFIER

Mon tableau de départ:

 Tableau 
 (
 [_319_] => Tableau 
 (
 [Id] => 0 
 [Parent_id] => 0 
) 
 
 [_320_] => Tableau 
 (
 [Id] => _320 _ 
 [Parent_id] => 0 
) 
 
 [_321_] => Tableau 
 (
 [Id] => _321 _ 
 [Parent_id] => _320 _ 
) 
 
 [_322_] => Tableau 
 (
 [Id] => _322 _ 
 [Id_parent] => _321 _ 
) 
 
 [_323_] => Tableau 
 (
 [Id] => _323 _ 
 [Id_parent] => 0 
) 
 
 [_324_] => Tableau 
 (
 [Id] => _324 _ 
 [Parent_id] => _323 _ 
) 
 
 [_325_] => Tableau 
 (
 [Id] => _325 _ 
 [Id_parent] => _320 _ 
) 
)

Le tableau résultant après la création de l'arbre:

 Tableau 
 (
 [_319_] => Tableau 
 (
 [Id] => _319 _ 
 [Parent_id] => 0 
) 
 
 [_320_] => Tableau 
 (
 [Id] => _320 _ 
 [Parent_id] => 0 
 [Enfants] => Tableau 
 (
 [_321_] => Tableau 
 (
 [Id] => _321 _ 
 [parent_id] => _320 _ 
 [children] => Array 
 (
 [_322_] => Array 
 (
 [id] = > _322 _ 
 [Parent_id] => _321 _ 
) 
) 
) 
 [_325_] => Tableau 
 (
 [Id] => _325 _ 
 [Parent_id] => _320 _ 
) 
) 
 [_323_] => Tableau 
 (
 [Id] => _323 _ 
 [Parent_id] => 0 
 [Enfants] => Tableau 
 (
 [_324_] => Tableau 
 (
 [Id] => _324 _ 
 [Parent_id] => _323 _ 
) 
) 
) 

Toute aide/orientation est grandement appréciée!

Un code que j'ai jusqu'à présent:

 
 fonction buildTree (tableau & éléments $, $ parentId = 0) {
 $ branch = array (); 
 
 foreach ($ elements as $ element) {
 if ($ element ['parent_id'] == $ parentId) {
 $ children = $ this-> buildTree ($ elements, $ element ['id'])) ; 
 if ($ children) {
 $ element ['children'] = $ children; 
} 
 $ branch [] = $ element; 
} 
} 
 
 retourne $ branch; 
} 
 
34
DSkinner

Vous avez oublié la unset() là-dedans bro.

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['id']] = $element;
            unset($elements[$element['id']]);
        }
    }
    return $branch;
}
51
n0nag0n

La solution d'ImmortalFirefly fonctionne cependant, comme le souligne mrded, elle ne sauve pas les premiers parents sans enfants. J'ai édité la fonction pour résoudre ce problème:

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['id']] = $element;
            unset($element);
        }
    }
    return $branch;
}
27
SteveEdson

Cela fonctionne pour moi:

$index=array();
$tree=array();
foreach ($ori as $key=>$var) {
  $var=array_shift($ori);
  if ($var['id']==0) $var['id']=$key;
  if ((string)$var['parent_id']==='0') {
    $tree[$key]=$var;
    $index[$key]=&$tree[$key];
  } else if (isset($index[$var['parent_id']])) {
    if (!isset($index[$var['parent_id']]['children'])) $index[$var['parent_id']]['children']=array();
    $index[$var['parent_id']]['children'][$key]=$var;
    $index[$key]=&$index[$var['parent_id']]['children'][$key];
  } else {
    array_Push($ori,$var);
  }
}
unset($index);
print_r($tree);
6
Eugen Rieck

Je peux voir la logique, sauf pour cela dans le résultat:

Array
(
    [0] => Array
        (
            [id] => 0
            [parent_id] => 0
        )

    [1] => Array
        (
            [id] => 1
            [parent_id] => 0
        )

À mon humble avis, parent_id = o, ne devrait pas [1] être un enfant de [0] ici?

Quoi qu'il en soit, des références à la rescousse:

$tree = array();
foreach($inputarray as $item){
     if(!isset($tree[$item['id']])) $tree[$item['id']] = array();
     $tree[$item['id']] = array_merge($tree[$item['id']],$item);
     if(!isset($tree[$item['parent_id']])) $tree[$item['parent_id']] = array();
     if(!isset($tree[$item['parent_id']]['children'])) $tree[$item['parent_id']]['children'] = array();
     $tree[$item['parent_id']]['children'][] = &$tree[$item['id']];
}
$result = $tree[0]['children'];
unset($tree);
print_r($result);

Parce que vous avez abusé de 0 en tant que nombre "magique" en tant que root et en tant qu'identifiant existant, nous avons maintenant une récursivité dans la branche id = 0. L'ajout de if($item['parent_id']!=$item['id']) avant $tree[$item['parent_id']]['children'][] = &$tree[$item['id']]; Pourrait empêcher cela, mais ce n'est pas joli.

3
Wrikken

Il est possible de construire le tableau source légèrement différent vous pouvez utiliser cette fonction (parent_id, id, title):

$q = mysql_query("SELECT id, parent_id, name FROM categories");
while ($r = mysql_fetch_row($q)) {
  $names[$r[0]] = $r[2];
  $children[$r[0]][] = $r[1];
 }

function render_select($root=0, $level=-1) {
  global $names, $children;
  if ($root != 0)
    echo '<option>' . strrep(' ', $level) . $names[$root] . '</option>';
  foreach ($children[$root] as $child)
    render_select($child, $level+1);
}

echo '<select>';
render_select();
echo '</select>';
  1. Système de hiérarchie plus efficace
2
Gigamegs

Bien que ce soit une vieille question, je vais poster ma réponse ici:

/* assuming top level pid = 0 */
$rows = array (
    array ( 'id' => 1, 'pid' => 0 ),
    /* ... */
);

/* make id become array key */
$rows = array_column ( $rows, null, 'id' ); 

foreach ( $rows as $key => $val ) {
    if ( $val ['pid'] ) {
        if ( isset ( $rows [$val ['pid']] )) {
            $rows [$val ['pid']]['children'][] = &$rows [$key];
        }
    }
}

foreach ( $rows as $key => $val ) {
    if ( $val ['pid'] ) unset ( $rows [$key] );
}

array_column est PHP 5.5 mais vous pouvez créer le vôtre facilement.

2
Inglis Baderson

Le code de SteveEdson fonctionne correctement, sauf dans le cas où le parent d'un élément n'existe pas dans la structure de données d'origine. Voici ma correction pour cela (cependant, il supprime "parent_id" des éléments, qui peuvent ou non être acceptables):

function buildTree(array &$elements, $parentId = 0)
{
    $branch = array();
    foreach ($elements as &$element) {
        if ($element["parent_id"] != null && $elements[$element["parent_id"]] == null)
            unset($element["parent_id"]);        
        if ($element['parent_id'] == $parentId) {
            $children = buildTree($elements, $element['id']);
            if ($children) {
                $element['children'] = $children;
            }
            $branch[$element['id']] = $element;
            unset($element);
        }
    }
    return $branch;
}
1
Arne M

Vous voulez envisager de stocker et de charger des données hiérarchiques dans MySQL car cela devrait résoudre quelques problèmes. Je suppose que le premier tableau représente des données directement extraites de la base de données?

Il semble que vous essayez d'utiliser le modèle de contiguïté pour organiser vos données dans la structure hiérarchique. Il existe également d'autres moyens d'y parvenir à l'aide de l'imbrication. Si vous ne prenez pas ces données d'une base de données, cela peut ne pas être aussi utile.

Ce lien devrait vous aider: http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/

1
Daniel West

Voici ma solution, fonctionne idéalement, si nous supposons que le parent_id de niveau supérieur = 0:

function MakeTree($arr){
    $parents_arr=array();
    foreach ($arr as $key => $value) {
        $parents_arr[$value['pid']][$value['id']]=$value;
    }
    $tree=$parents_arr['0'];
    $this->createTree($tree, $parents_arr);
    return $tree;
}
function createTree(&$tree, $parents_arr){
    foreach ($tree as $key => $value) {
        if(!isset($value['children'])) {
            $tree[$key]['children']=array();
        }
        if(array_key_exists($key, $parents_arr)){
            $tree[$key]['children']=$parents_arr[$key];
            $this->createTree($tree[$key]['children'], $parents_arr);
        }
    }
}
0
user628176

Propre, court et sans ballast. Tableau de tableaux à arborescence:

class Mother {
    private $root;
    public function treeInit($array)
    {
        $this->root = new Child();
        foreach($array as $value){
            $this->root->treeClimb(array_reverse($value));
        }
        return $this->root;
    }
}

class Child {
    private $children = [];
    public function treeClimb($arr)
    {
        if(count($arr) > 0) {
            $childTmp = array_pop($arr);
            if(!key_exists($childTmp,$this->children))
            {
                $this->children[$childTmp] = new Child();
            }
        $this->children[$childTmp]->treeClimb($arr);
        }
    }
}

$array = array(array('obst','banae','krumm','gelb'),
                    array('obst','beere','him'),
                    array('obst','beere','brom'),
                    array('obst','banae','gerade'),
                    array('veg','carot','gerade'));

$obj = new Mother();
var_dump($obj->treeInit($array));
0

Ceci est ma solution, copiez et optimisez d'autres solutions.

function buildTree(array &$elements, $parentId = 0) {
    $branch = array();
    foreach ($elements as $key => $element) {
        if ($element['parent_id'] == $parentId) {
            $children = $this->buildTree($elements, $key);
            if ($children) {
                $element['children'] = $children;
            }
            $branch[$key] = $element;
            unset($elements[$key]);
        }
    }
    return $branch;
}
0
touzas

J'ai trouvé une solution similaire à @ eugen-rieck et j'ai voulu la partager. J'ai nommé $branches mon tableau d'indices, cependant.

$tree = [];
$branches = [];

while (!empty($input)) {
    $beforeCount = count($input);

    foreach ($input as $id => $item) {
        $pid = $item['parent_id'];

        if (isset($branches[$pid])) {
            $branches[$pid]['children'][$id] = $item;
            $branches[$id] = &$branches[$pid]['children'][$id];
            unset($input[$id]);
        }
    }

    if ($beforeCount === count($input)) {
        $firstItem = array_shift($input);
        $id = $firstItem['id'];
        $tree[$id] = $firstItem;
        $branches[$id] = &$tree[$id];
    }
}
0
hoorider