Convertir une série de relations parent-enfant en un arbre hiérarchique?

100

J'ai un tas de paires nom-nom parent, que j'aimerais transformer en aussi peu de structures arborescentes héritières que possible. Ainsi, par exemple, ceux-ci pourraient être les appariements:

Child : Parent
    H : G
    F : G
    G : D
    E : D
    A : E
    B : C
    C : E
    D : NULL

Qui doit être transformé en (un) arbre (s) héritier (s):

D
├── E
   ├── A
      └── B
   └── C   
└── G
    ├── F
    └── H

Le résultat final que je souhaite est un ensemble d' <ul>éléments imbriqués , chacun <li>contenant le nom de l'enfant.

Il n'y a pas d'incohérences dans les appariements (l'enfant est son propre parent, le parent est l'enfant de l'enfant, etc.), donc un tas d'optimisations peut probablement être fait.

Comment, en PHP, passerais-je d'un tableau contenant des paires enfant => parent, à un ensemble de <ul>s imbriqués ?

J'ai le sentiment que la récursion est impliquée, mais je ne suis pas assez éveillé pour y réfléchir.

Eric
la source

Réponses:

129

Cela nécessite une fonction récursive très basique pour analyser les paires enfant / parent en une structure arborescente et une autre fonction récursive pour l'imprimer. Une seule fonction suffirait mais en voici deux pour plus de clarté (une fonction combinée peut être trouvée à la fin de cette réponse).

Initialisez d'abord le tableau de paires enfant / parent:

$tree = array(
    'H' => 'G',
    'F' => 'G',
    'G' => 'D',
    'E' => 'D',
    'A' => 'E',
    'B' => 'C',
    'C' => 'E',
    'D' => null
);

Ensuite, la fonction qui analyse ce tableau dans une structure arborescente hiérarchique:

function parseTree($tree, $root = null) {
    $return = array();
    # Traverse the tree and search for direct children of the root
    foreach($tree as $child => $parent) {
        # A direct child is found
        if($parent == $root) {
            # Remove item from tree (we don't need to traverse this again)
            unset($tree[$child]);
            # Append the child into result array and parse its children
            $return[] = array(
                'name' => $child,
                'children' => parseTree($tree, $child)
            );
        }
    }
    return empty($return) ? null : $return;    
}

Et une fonction qui parcourt cet arbre pour imprimer une liste non ordonnée:

function printTree($tree) {
    if(!is_null($tree) && count($tree) > 0) {
        echo '<ul>';
        foreach($tree as $node) {
            echo '<li>'.$node['name'];
            printTree($node['children']);
            echo '</li>';
        }
        echo '</ul>';
    }
}

Et l'utilisation réelle:

$result = parseTree($tree);
printTree($result);

Voici le contenu de $result:

Array(
    [0] => Array(
        [name] => D
        [children] => Array(
            [0] => Array(
                [name] => G
                [children] => Array(
                    [0] => Array(
                        [name] => H
                        [children] => NULL
                    )
                    [1] => Array(
                        [name] => F
                        [children] => NULL
                    )
                )
            )
            [1] => Array(
                [name] => E
                [children] => Array(
                    [0] => Array(
                        [name] => A
                        [children] => NULL
                    )
                    [1] => Array(
                        [name] => C
                        [children] => Array(
                            [0] => Array(
                                [name] => B
                                [children] => NULL
                            )
                        )
                    )
                )
            )
        )
    )
)

Si vous voulez un peu plus d'efficacité, vous pouvez combiner ces fonctions en une seule et réduire le nombre d'itérations effectuées:

function parseAndPrintTree($root, $tree) {
    $return = array();
    if(!is_null($tree) && count($tree) > 0) {
        echo '<ul>';
        foreach($tree as $child => $parent) {
            if($parent == $root) {                    
                unset($tree[$child]);
                echo '<li>'.$child;
                parseAndPrintTree($child, $tree);
                echo '</li>';
            }
        }
        echo '</ul>';
    }
}

Vous ne sauvegarderez que 8 itérations sur un ensemble de données aussi petit que celui-ci, mais sur des ensembles plus grands, cela pourrait faire une différence.

Tatu Ulmanen
la source
2
Tatu. Comment pourrais-je changer la fonction printTree pour ne pas faire écho directement au code HTML de l'arbre mais enregistrer tout le code HTML de sortie dans une variable et le renvoyer? merci
Enrique
Bonjour, je pense que la déclaration de fonction doit être parseAndPrintTree ($ tree, $ root = null) et que l'appel récursif doit être parseAndPrintTree ($ child, $ tree); Meilleures salutations
razor7
55

Encore une autre fonction pour créer un arbre (aucune récursivité impliquée, utilise des références à la place):

$array = array('H' => 'G', 'F' => 'G', ..., 'D' => null);

function to_tree($array)
{
    $flat = array();
    $tree = array();

    foreach ($array as $child => $parent) {
        if (!isset($flat[$child])) {
            $flat[$child] = array();
        }
        if (!empty($parent)) {
            $flat[$parent][$child] =& $flat[$child];
        } else {
            $tree[$child] =& $flat[$child];
        }
    }

    return $tree;
}

Renvoie un tableau hiérarchique comme celui-ci:

Array(
    [D] => Array(
        [G] => Array(
            [H] => Array()
            [F] => Array()
        )
        ...
    )
)

Qui peut facilement être imprimé sous forme de liste HTML en utilisant la fonction récursive.

Alexandre Konstantinov
la source
+1 - Très intelligent. Bien que je trouve la solution récursive plus logique. Mais je préfère le format de sortie de votre fonction.
Eric
@Eric plus logique? Je ne suis pas d'accord. Il n'y a rien de «logique» dans la récursivité; il y a OTOH une surcharge cognitive sévère sur l'analyse des fonctions / appels récursifs. S'il n'y a pas d'allocation de pile explicite, je prendrais chaque jour une itération sur la récursivité.
29

Une autre manière plus simplifiée de convertir la structure plate $treedans une hiérarchie. Un seul tableau temporaire est nécessaire pour l'exposer:

// add children to parents
$flat = array(); # temporary array
foreach ($tree as $name => $parent)
{
    $flat[$name]['name'] = $name; # self
    if (NULL === $parent)
    {
        # no parent, is root element, assign it to $tree
        $tree = &$flat[$name]; 
    }
    else
    {
        # has parent, add self as child    
        $flat[$parent]['children'][] = &$flat[$name];
    }
}
unset($flat);

C'est tout pour obtenir la hiérarchie dans un tableau multidimensionnel:

Array
(
    [children] => Array
        (
            [0] => Array
                (
                    [children] => Array
                        (
                            [0] => Array
                                (
                                    [name] => H
                                )

                            [1] => Array
                                (
                                    [name] => F
                                )

                        )

                    [name] => G
                )

            [1] => Array
                (
                    [name] => E
                    [children] => Array
                        (
                            [0] => Array
                                (
                                    [name] => A
                                )

                            [1] => Array
                                (
                                    [children] => Array
                                        (
                                            [0] => Array
                                                (
                                                    [name] => B
                                                )

                                        )

                                    [name] => C
                                )

                        )

                )

        )

    [name] => D
)

La sortie est moins triviale si vous voulez éviter la récursivité (peut être un fardeau avec de grandes structures).

J'ai toujours voulu résoudre le «dilemme» UL / LI pour la sortie d'un tableau. Le dilemme est que chaque élément ne sait pas si les enfants suivront ou non ou combien d'éléments précédents doivent être fermés. Dans une autre réponse, j'ai déjà résolu cela en utilisant un RecursiveIteratorIteratoret en recherchant getDepth()et d'autres méta-informations que ma propre écriture a Iteratorfournies: Obtenir un modèle d'ensemble imbriqué dans un sous- <ul>arbre «fermé» mais en le cachant . Cette réponse montre également qu'avec les itérateurs, vous êtes assez flexible.

Cependant, c'était une liste pré-triée, donc ne conviendrait pas à votre exemple. De plus, j'ai toujours voulu résoudre ce problème pour une sorte d'arborescence standard, du HTML <ul>et des <li>éléments.

Le concept de base que j'ai proposé est le suivant:

  1. TreeNode- Abstractionne chaque élément dans un TreeNodetype simple qui peut fournir sa valeur (par exemple Name) et s'il a ou non des enfants.
  2. TreeNodesIterator- Un RecursiveIteratorqui est capable d'itérer sur un ensemble (tableau) de ceux-ci TreeNodes. C'est assez simple car le TreeNodetype sait déjà s'il a des enfants et lesquels.
  3. RecursiveListIterator- Un RecursiveIteratorIteratorqui a tous les événements nécessaires lorsqu'il itère récursivement sur tout type de RecursiveIterator:
    • beginIteration/ endIteration- Début et fin de la liste principale.
    • beginElement/ endElement- Début et fin de chaque élément.
    • beginChildren/ endChildren- Début et fin de chaque liste d'enfants. Cela RecursiveListIteratorne fournit ces événements que sous forme d'appels de fonction. les listes d'enfants, comme c'est typique pour les <ul><li>listes, sont ouvertes et fermées à l'intérieur de son <li>élément parent . Par conséquent, l' endElementévénement est déclenché après l' endChildrenévénement correspondant. Cela pourrait être modifié ou rendu configurable pour élargir l'utilisation de cette classe. Les événements sont ensuite distribués sous forme d'appels de fonction à un objet décorateur, pour séparer les choses.
  4. ListDecorator- Une classe "décorateur" qui n'est qu'un récepteur des événements de RecursiveListIterator.

Je commence par la logique de sortie principale. Prenant le $treetableau maintenant hiérarchique , le code final ressemble à ce qui suit:

$root = new TreeNode($tree);
$it = new TreeNodesIterator(array($root));
$rit = new RecursiveListIterator($it);
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);

foreach($rit as $item)
{
    $inset = $decor->inset(1);
    printf("%s%s\n", $inset, $item->getName());
}

Tout d' abord le regard de let dans le ListDecoratorque le simple enveloppe les <ul>et <li>éléments et décider de la façon dont la structure de la liste est sortie:

class ListDecorator
{
    private $iterator;
    public function __construct(RecursiveListIterator $iterator)
    {
        $this->iterator = $iterator;
    }
    public function inset($add = 0)
    {
        return str_repeat('  ', $this->iterator->getDepth()*2+$add);
    }

Le constructeur prend l'itérateur de liste sur lequel il travaille. insetest juste une fonction d'aide pour une belle indentation de la sortie. Le reste n'est que les fonctions de sortie pour chaque événement:

    public function beginElement()
    {
        printf("%s<li>\n", $this->inset());
    }
    public function endElement()
    {
        printf("%s</li>\n", $this->inset());
    }
    public function beginChildren()
    {
        printf("%s<ul>\n", $this->inset(-1));
    }
    public function endChildren()
    {
        printf("%s</ul>\n", $this->inset(-1));
    }
    public function beginIteration()
    {
        printf("%s<ul>\n", $this->inset());
    }
    public function endIteration()
    {
        printf("%s</ul>\n", $this->inset());
    }
}

Avec ces fonctions de sortie à l'esprit, c'est à nouveau la boucle / boucle de sortie principale, je la parcoure pas à pas:

$root = new TreeNode($tree);

Créez la racine TreeNodequi sera utilisée pour démarrer l'itération sur:

$it = new TreeNodesIterator(array($root));

C'est TreeNodesIteratorun RecursiveIteratorqui permet une itération récursive sur le $rootnœud unique . Il est passé sous forme de tableau car cette classe a besoin de quelque chose pour itérer et permet la réutilisation avec un ensemble d'enfants qui est également un tableau d' TreeNodeéléments.

$rit = new RecursiveListIterator($it);

C'est RecursiveListIteratorun RecursiveIteratorIteratorqui fournit lesdits événements. Pour l'utiliser, il suffit ListDecoratorde fournir un (la classe ci-dessus) et de lui attribuer addDecorator:

$decor = new ListDecorator($rit);
$rit->addDecorator($decor);

Ensuite, tout est configuré pour juste au- foreachdessus et afficher chaque nœud:

foreach($rit as $item)
{
    $inset = $decor->inset(1);
    printf("%s%s\n", $inset, $item->getName());
}

Comme le montre cet exemple, toute la logique de sortie est encapsulée dans la ListDecoratorclasse et ce single foreach. L'ensemble du parcours récursif a été entièrement encapsulé dans des itérateurs récursifs SPL qui ont fourni une procédure empilée, ce qui signifie qu'en interne aucun appel de fonction de récursivité n'est effectué.

L'événement basé ListDecoratorvous permet de modifier spécifiquement la sortie et de fournir plusieurs types de listes pour la même structure de données. Il est même possible de modifier l'entrée car les données du tableau ont été encapsulées dans TreeNode.

L'exemple de code complet:

<?php
namespace My;

$tree = array('H' => 'G', 'F' => 'G', 'G' => 'D', 'E' => 'D', 'A' => 'E', 'B' => 'C', 'C' => 'E', 'D' => null);

// add children to parents
$flat = array(); # temporary array
foreach ($tree as $name => $parent)
{
    $flat[$name]['name'] = $name; # self
    if (NULL === $parent)
    {
        # no parent, is root element, assign it to $tree
        $tree = &$flat[$name];
    }
    else
    {
        # has parent, add self as child    
        $flat[$parent]['children'][] = &$flat[$name];
    }
}
unset($flat);

class TreeNode
{
    protected $data;
    public function __construct(array $element)
    {
        if (!isset($element['name']))
            throw new InvalidArgumentException('Element has no name.');

        if (isset($element['children']) && !is_array($element['children']))
            throw new InvalidArgumentException('Element has invalid children.');

        $this->data = $element;
    }
    public function getName()
    {
         return $this->data['name'];
    }
    public function hasChildren()
    {
        return isset($this->data['children']) && count($this->data['children']);
    }
    /**
     * @return array of child TreeNode elements 
     */
    public function getChildren()
    {        
        $children = $this->hasChildren() ? $this->data['children'] : array();
        $class = get_called_class();
        foreach($children as &$element)
        {
            $element = new $class($element);
        }
        unset($element);        
        return $children;
    }
}

class TreeNodesIterator implements \RecursiveIterator
{
    private $nodes;
    public function __construct(array $nodes)
    {
        $this->nodes = new \ArrayIterator($nodes);
    }
    public function  getInnerIterator()
    {
        return $this->nodes;
    }
    public function getChildren()
    {
        return new TreeNodesIterator($this->nodes->current()->getChildren());
    }
    public function hasChildren()
    {
        return $this->nodes->current()->hasChildren();
    }
    public function rewind()
    {
        $this->nodes->rewind();
    }
    public function valid()
    {
        return $this->nodes->valid();
    }   
    public function current()
    {
        return $this->nodes->current();
    }
    public function key()
    {
        return $this->nodes->key();
    }
    public function next()
    {
        return $this->nodes->next();
    }
}

class RecursiveListIterator extends \RecursiveIteratorIterator
{
    private $elements;
    /**
     * @var ListDecorator
     */
    private $decorator;
    public function addDecorator(ListDecorator $decorator)
    {
        $this->decorator = $decorator;
    }
    public function __construct($iterator, $mode = \RecursiveIteratorIterator::SELF_FIRST, $flags = 0)
    {
        parent::__construct($iterator, $mode, $flags);
    }
    private function event($name)
    {
        // event debug code: printf("--- %'.-20s --- (Depth: %d, Element: %d)\n", $name, $this->getDepth(), @$this->elements[$this->getDepth()]);
        $callback = array($this->decorator, $name);
        is_callable($callback) && call_user_func($callback);
    }
    public function beginElement()
    {
        $this->event('beginElement');
    }
    public function beginChildren()
    {
        $this->event('beginChildren');
    }
    public function endChildren()
    {
        $this->testEndElement();
        $this->event('endChildren');
    }
    private function testEndElement($depthOffset = 0)
    {
        $depth = $this->getDepth() + $depthOffset;      
        isset($this->elements[$depth]) || $this->elements[$depth] = 0;
        $this->elements[$depth] && $this->event('endElement');

    }
    public function nextElement()
    {
        $this->testEndElement();
        $this->event('{nextElement}');
        $this->event('beginElement');       
        $this->elements[$this->getDepth()] = 1;
    } 
    public function beginIteration()
    {
        $this->event('beginIteration');
    }
    public function endIteration()
    {
        $this->testEndElement();
        $this->event('endIteration');       
    }
}

class ListDecorator
{
    private $iterator;
    public function __construct(RecursiveListIterator $iterator)
    {
        $this->iterator = $iterator;
    }
    public function inset($add = 0)
    {
        return str_repeat('  ', $this->iterator->getDepth()*2+$add);
    }
    public function beginElement()
    {
        printf("%s<li>\n", $this->inset(1));
    }
    public function endElement()
    {
        printf("%s</li>\n", $this->inset(1));
    }
    public function beginChildren()
    {
        printf("%s<ul>\n", $this->inset());
    }
    public function endChildren()
    {
        printf("%s</ul>\n", $this->inset());
    }
    public function beginIteration()
    {
        printf("%s<ul>\n", $this->inset());
    }
    public function endIteration()
    {
        printf("%s</ul>\n", $this->inset());
    }
}


$root = new TreeNode($tree);
$it = new TreeNodesIterator(array($root));
$rit = new RecursiveListIterator($it);
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);

foreach($rit as $item)
{
    $inset = $decor->inset(2);
    printf("%s%s\n", $inset, $item->getName());
}

Outpupt:

<ul>
  <li>
    D
    <ul>
      <li>
        G
        <ul>
          <li>
            H
          </li>
          <li>
            F
          </li>
        </ul>
      </li>
      <li>
        E
        <ul>
          </li>
          <li>
            A
          </li>
          <li>
            C
            <ul>
              <li>
                B
              </li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

Démo (variante PHP 5.2)

Une variante possible serait un itérateur qui itère sur tout RecursiveIteratoret fournit une itération sur tous les événements qui peuvent se produire. Un commutateur / boîtier à l'intérieur de la boucle foreach pourrait alors gérer les événements.

En relation:

hakre
la source
3
Aussi «bien conçue» que soit cette solution - comment est-elle exactement «d'une manière plus simplifiée» que les exemples précédents - Cela semble juste être une solution surdimensionnée au même problème
André
@Andre: Par le grade d'encapsulation IIRC. Dans une autre réponse connexe, j'ai un fragment de code entièrement non encapsulé qui est beaucoup plus petit et pourrait donc être "plus simplifié" en fonction du POV.
hakre
@hakre Comment puis-je modifier la classe "ListDecorator" pour ajouter "id" à LI, qui est extrait du tableau d'arborescence?
Gangesh
1
@Gangesh: Le plus facilement avec un vistor de nœud. ^^ Blaguez un peu, le simple fait d'étendre le décorateur et de modifier beginElement (), d'obtenir l'itérateur interne (voir la méthode inset () pour un exemple) et le travail avec l'attribut id.
hakre
@hakre Merci. J'essaierai ça.
Gangesh
8

Eh bien, d'abord, je transformerais le tableau simple de paires clé-valeur en un tableau hiérarchique

function convertToHeiarchical(array $input) {
    $parents = array();
    $root = array();
    $children = array();
    foreach ($input as $item) {
        $parents[$item['id']] = &$item;
        if ($item['parent_id']) {
            if (!isset($children[$item['parent_id']])) {
                $children[$item['parent_id']] = array();
            }
            $children[$item['parent_id']][] = &$item;
        } else {
            $root = $item['id'];
        }
    }
    foreach ($parents as $id => &$item) {
        if (isset($children[$id])) {
            $item['children'] = $children[$id];
        } else {
            $item['children'] = array();
        }
    }
    return $parents[$root];
}

Cela peut convertir un tableau plat avec parent_id et id en un tableau hiérarchique:

$item = array(
    'id' => 'A',
    'blah' => 'blah',
    'children' => array(
        array(
            'id' => 'B',
            'blah' => 'blah',
            'children' => array(
                array(
                    'id' => 'C',
                    'blah' => 'blah',
                    'children' => array(),
                ),
             ),
            'id' => 'D',
            'blah' => 'blah',
            'children' => array(
                array(
                    'id' => 'E',
                    'blah' => 'blah',
                    'children' => array(),
                ),
            ),
        ),
    ),
);

Ensuite, créez simplement une fonction de rendu:

function renderItem($item) {
    $out = "Your OUtput For Each Item Here";
    $out .= "<ul>";
    foreach ($item['children'] as $child) {
        $out .= "<li>".renderItem($child)."</li>";
    }
    $out .= "</ul>";
    return $out;
}
ircmaxell
la source
5

Bien que la solution d' Alexander-Konstantinov puisse ne pas sembler aussi facile à lire au début, elle est à la fois géniale et exponentiellement meilleure en termes de performances, cela aurait dû être élue comme la meilleure réponse.

Merci mon pote, j'ai fait un benchmark en votre honneur pour comparer les 2 solutions présentées dans ce post.

J'avais un arbre plat @ 250k avec 6 niveaux que je devais convertir et je cherchais un meilleur moyen de le faire et d'éviter les itérations récursives.

Récursivité vs référence:

// Generate a 6 level flat tree
$root = null;
$lvl1 = 13;
$lvl2 = 11;
$lvl3 = 7;
$lvl4 = 5;
$lvl5 = 3;
$lvl6 = 1;    
$flatTree = [];
for ($i = 1; $i <= 450000; $i++) {
    if ($i % 3 == 0)  { $lvl5 = $i; $flatTree[$lvl6] = $lvl5; continue; }
    if ($i % 5 == 0)  { $lvl4 = $i; $flatTree[$lvl5] = $lvl4; continue; }
    if ($i % 7 == 0)  { $lvl3 = $i; $flatTree[$lvl3] = $lvl2; continue; }
    if ($i % 11 == 0) { $lvl2 = $i; $flatTree[$lvl2] = $lvl1; continue; }
    if ($i % 13 == 0) { $lvl1 = $i; $flatTree[$lvl1] = $root; continue; }
    $lvl6 = $i;
}

echo 'Array count: ', count($flatTree), PHP_EOL;

// Reference function
function treeByReference($flatTree)
{
    $flat = [];
    $tree = [];

    foreach ($flatTree as $child => $parent) {
        if (!isset($flat[$child])) {
            $flat[$child] = [];
        }
        if (!empty($parent)) {
            $flat[$parent][$child] =& $flat[$child];
        } else {
            $tree[$child] =& $flat[$child];
        }
    }

    return $tree;
}

// Recursion function
function treeByRecursion($flatTree, $root = null)
{
    $return = [];
    foreach($flatTree as $child => $parent) {
        if ($parent == $root) {
            unset($flatTree[$child]);
            $return[$child] = treeByRecursion($flatTree, $child);
        }
    }
    return $return ?: [];
}

// Benchmark reference
$t1 = microtime(true);
$tree = treeByReference($flatTree);
echo 'Reference: ', (microtime(true) - $t1), PHP_EOL;

// Benchmark recursion
$t2 = microtime(true);
$tree = treeByRecursion($flatTree);
echo 'Recursion: ', (microtime(true) - $t2), PHP_EOL;

La sortie parle d'elle-même:

Array count: 255493
Reference: 0.3259289264679 (less than 0.4s)
Recursion: 6604.9865279198 (almost 2h)
ntt
la source
2

Eh bien, pour analyser les UL et les LI, ce serait quelque chose comme:

$array = array (
    'H' => 'G'
    'F' => 'G'
    'G' => 'D'
    'E' => 'D'
    'A' => 'E'
    'B' => 'C'
    'C' => 'E'
    'D' => 'NULL'
);


recurse_uls ($array, 'NULL');

function recurse_uls ($array, $parent)
{
    echo '<ul>';
    foreach ($array as $c => $p)  {
        if ($p != $parent) continue;
        echo '<li>'.$c.'</li>';
        recurse_uls ($array, $c);
    }
    echo '</ul>';
}

Mais j'aimerais voir une solution qui ne vous oblige pas à parcourir le tableau si souvent ...

Arnorhs
la source
2

Voici ce que j'ai trouvé:

$arr = array(
            'H' => 'G',
            'F' => 'G',
            'G' => 'D',
            'E' => 'D',
            'A' => 'E',
            'B' => 'C',
            'C' => 'E',
            'D' => null );

    $nested = parentChild($arr);
    print_r($nested);

    function parentChild(&$arr, $parent = false) {
      if( !$parent) { //initial call
         $rootKey = array_search( null, $arr);
         return array($rootKey => parentChild($arr, $rootKey));
      }else { // recursing through
        $keys = array_keys($arr, $parent);
        $piece = array();
        if($keys) { // found children, so handle them
          if( !is_array($keys) ) { // only one child
            $piece = parentChild($arr, $keys);
           }else{ // multiple children
             foreach( $keys as $key ){
               $piece[$key] = parentChild($arr, $key);
             }
           }
        }else {
           return $parent; //return the main tag (no kids)
        }
        return $piece; // return the array built via recursion
      }
    }

les sorties:

Array
(
    [D] => Array
        (
            [G] => Array
                (
                    [H] => H
                    [F] => F
                )

            [E] => Array
                (
                    [A] => A
                    [C] => Array
                        (
                            [B] => B
                        )    
                )    
        )    
)
Dan Heberden
la source
1

Relation parent-enfant nested Array
Récupère tous les enregistrements de la base de données et crée un tableau imbriqué.

$data = SampleTable::find()->all();
$tree = buildTree($data);
print_r($tree);

public function buildTree(array $elements, $parentId = 0) {
    $branch = array();
    foreach ($elements as $element) {
        if ($element['iParentId'] == $parentId) {
            $children =buildTree($elements, $element['iCategoriesId']);
            if ($children) {
                $element['children'] = $children;
            }
            $branch[] = $element;
        }
    }
    return $branch;
}

Imprimer les données des catégories et sous-catégories au format json

public static function buildTree(array $elements, $parentId = 0){
    $branch = array();
    foreach($elements as $element){
        if($element['iParentId']==$parentId){
            $children =buildTree($elements, $element['iCategoriesId']);
            if ($children) {
                $element['children'] = $children;

            }
                $branch[] = array(
                    'iCategoriesId' => $element->iCategoriesId,
                    'iParentId'=>$element->iParentId,
                    'vCategoriesName'=>$element->vCategoriesName,
                    'children'=>$element->children,
            );
        }
    }
    return[
        $branch
    ];
}
Akshay Mistry
la source
0
$tree = array(
    'H' => 'G',
    'F' => 'G',
    'G' => 'D',
    'E' => 'D',
    'A' => 'E',
    'B' => 'C',
    'C' => 'E',
    'D' => null,
    'Z' => null,
    'MM' =>'Z',
    'KK' =>'Z',
    'MMM' =>'MM',
    // 'MM'=>'DDD'
);

$ aa = $ this-> parseTree ($ tree);

public function get_tress($tree,$key)
{

    $x=array();
    foreach ($tree as $keys => $value) {
        if($value==$key){
        $x[]=($keys);
        }
    }
    echo "<li>";
    foreach ($x as $ke => $val) {
    echo "<ul>";
        echo($val);
        $this->get_tress($tree,$val);
    echo "</ul>";
    }
    echo "</li>";


}
function parseTree($tree, $root = null) {

    foreach ($tree as $key => $value) {
        if($value==$root){

            echo "<ul>";
            echo($key);
            $this->get_tress($tree,$key);
            echo "</ul>";
        }
    }
Bharat
la source
0

Ancienne question, mais moi aussi j'ai dû le faire et les exemples avec récursivité m'ont donné mal à la tête. Dans ma base de données, nous avons une locationstable, qui était un loca_idPK (enfant) et un auto-référencement loca_parent_id(parent).

Le but est de représenter cette structure en HTML. La simple requête peut de couyrse renvoyer les données dans un ordre fixe mais je n'ai pas trouvé assez bien pour afficher ces données de manière naturelle. Ce que je voulais vraiment, c'était la gestion de l'arborescence Oracle avec LEVELpour aider à l'affichage.

J'ai décidé d'utiliser l'idée d'un «chemin» pour identifier de manière unique chaque entrée. Par exemple:

Le tri du tableau par chemin devrait faciliter le traitement pour un affichage significatif.

Je me rends compte que l'utilisation de tableaux et de tris associatifs est une triche car elle cache la complexité récursive des opérations, mais pour moi, cela semble plus simple:

<table>
<?php
    
    $sql = "
    
    SELECT l.*,
           pl.loca_name parent_loca_name,
           '' loca_path
    FROM locations l
    LEFT JOIN locations pl ON l.loca_parent_id = pl.loca_id
    ORDER BY l.loca_parent_id, l.loca_id
    
    ";
    
    function print_row ( $rowdata )
    {
    ?>
                      <tr>
                          <td>
                              <?=$rowdata['loca_id']?>
                          </td>
                          <td>
                              <?=$rowdata['loca_path']?>
                          </td>
                          <td>
                              <?=$rowdata['loca_type']?>
                          </td>
                          <td>
                              <?=$rowdata['loca_status']?>
                          </td>
                      </tr>
    <?php
    
    }
    
    $stmt  = $dbh->prepare($sql);
    $stmt->execute();
    $result = $stmt->get_result();
    $data = $result->fetch_all(MYSQLI_ASSOC);
    
    $printed = array();
    
    // To get tree hierarchy usually means recursion of data.
    // Here we will try to use an associate array and set a
    // 'path' value to represent the hierarchy tree in one
    // pass. Sorting this array by the path value should give
    // a nice tree order and reference.
// The array key will be the unique id (loca_id) for each row.
// The value for each key will the complete row from the database.
// The row contains a element 'loca_path' - we will write the path
// for each row here. A child's path will be parent_path/child_name.
// For any child we encounter with a parent we look up the parents path
// using the loca_parent_id as the key.
// Caveat, although tested quickly, just make sure that all parents are
// returned first by the query.
    
    foreach ($data as $row)
    {
    
       if ( $row['loca_parent_id'] == '' ) // Root Parent
       {
          $row['loca_path'] = $row['loca_name'] . '/';
          $printed[$row['loca_id']] = $row;
       }
       else // Child/Sub-Parent
       {
          $row['loca_path'] = $printed[$row['loca_parent_id']]['loca_path'] . $row['loca_name'] . '/';
          $printed[$row['loca_id']] = $row;
       }
    }
    
    // Array with paths built, now sort then print
    
    array_multisort(array_column($printed, 'loca_path'), SORT_ASC, $printed);
    
    foreach ( $printed as $prow )
    {
       print_row ( $prow );
    }
    ?>
    </table>
TenG
la source
-1

Comment créer une arborescence et un menu dynamiques

Étape 1: Nous allons d'abord créer une table d'arborescence dans la base de données mysql. ce tableau contient quatre colonnes. id est l'id de la tâche et nom est le nom de la tâche.

-
-- Table structure for table `treeview_items`
--

CREATE TABLE IF NOT EXISTS `treeview_items` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(200) NOT NULL,
  `title` varchar(200) NOT NULL,
  `parent_id` varchar(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ;

--
-- Dumping data for table `treeview_items`
--

INSERT INTO `treeview_items` (`id`, `name`, `title`, `parent_id`) VALUES
(1, 'task1', 'task1title', '2'),
(2, 'task2', 'task2title', '0'),
(3, 'task3', 'task1title3', '0'),
(4, 'task4', 'task2title4', '3'),
(5, 'task4', 'task1title4', '3'),
(6, 'task5', 'task2title5', '5');

Étape 2: Méthode récursive de l'arborescence J'ai créé ci-dessous la méthode createTreeView () de l'arborescence qui appelle récursive si l'ID de la tâche actuelle est supérieur à l'ID de la tâche précédente.

function createTreeView($array, $currentParent, $currLevel = 0, $prevLevel = -1) {

foreach ($array as $categoryId => $category) {

if ($currentParent == $category['parent_id']) {                       
    if ($currLevel > $prevLevel) echo " <ol class='tree'> "; 

    if ($currLevel == $prevLevel) echo " </li> ";

    echo '<li> <label for="subfolder2">'.$category['name'].'</label> <input type="checkbox" name="subfolder2"/>';

    if ($currLevel > $prevLevel) { $prevLevel = $currLevel; }

    $currLevel++; 

    createTreeView ($array, $categoryId, $currLevel, $prevLevel);

    $currLevel--;               
    }   

}

if ($currLevel == $prevLevel) echo " </li>  </ol> ";

}

Étape 3: Créez un fichier d'index pour afficher l'arborescence. Ceci est le fichier principal de l'exemple de treeview ici, nous allons appeler la méthode createTreeView () avec les paramètres requis.

 <body>
<link rel="stylesheet" type="text/css" href="_styles.css" media="screen">
<?php
mysql_connect('localhost', 'root');
mysql_select_db('test');


$qry="SELECT * FROM treeview_items";
$result=mysql_query($qry);


$arrayCategories = array();

while($row = mysql_fetch_assoc($result)){ 
 $arrayCategories[$row['id']] = array("parent_id" => $row['parent_id'], "name" =>                       
 $row['name']);   
  }
?>
<div id="content" class="general-style1">
<?php
if(mysql_num_rows($result)!=0)
{
?>
<?php 

createTreeView($arrayCategories, 0); ?>
<?php
}
?>

</div>
</body>

Étape 4: Créer un fichier CSS style.css Ici, nous allons écrire toutes les classes liées au CSS, actuellement j'utilise la liste de commande pour créer une arborescence. vous pouvez également modifier le chemin de l'image ici.

img { border: none; }
input, select, textarea, th, td { font-size: 1em; }

/* CSS Tree menu styles */
ol.tree
{
    padding: 0 0 0 30px;
    width: 300px;
}
    li 
    { 
        position: relative; 
        margin-left: -15px;
        list-style: none;
    }
    li.file
    {
        margin-left: -1px !important;
    }
        li.file a
        {
            background: url(document.png) 0 0 no-repeat;
            color: #fff;
            padding-left: 21px;
            text-decoration: none;
            display: block;
        }
        li.file a[href *= '.pdf']   { background: url(document.png) 0 0 no-repeat; }
        li.file a[href *= '.html']  { background: url(document.png) 0 0 no-repeat; }
        li.file a[href $= '.css']   { background: url(document.png) 0 0 no-repeat; }
        li.file a[href $= '.js']        { background: url(document.png) 0 0 no-repeat; }
    li input
    {
        position: absolute;
        left: 0;
        margin-left: 0;
        opacity: 0;
        z-index: 2;
        cursor: pointer;
        height: 1em;
        width: 1em;
        top: 0;
    }
        li input + ol
        {
            background: url(toggle-small-expand.png) 40px 0 no-repeat;
            margin: -0.938em 0 0 -44px; /* 15px */
            height: 1em;
        }
        li input + ol > li { display: none; margin-left: -14px !important; padding-left: 1px; }
    li label
    {
        background: url(folder-horizontal.png) 15px 1px no-repeat;
        cursor: pointer;
        display: block;
        padding-left: 37px;
    }

    li input:checked + ol
    {
        background: url(toggle-small.png) 40px 5px no-repeat;
        margin: -1.25em 0 0 -44px; /* 20px */
        padding: 1.563em 0 0 80px;
        height: auto;
    }
        li input:checked + ol > li { display: block; margin: 0 0 0.125em;  /* 2px */}
        li input:checked + ol > li:last-child { margin: 0 0 0.063em; /* 1px */ }

Plus de détails


la source