Convertire una serie di relazioni genitore-figlio in un albero gerarchico?


100

Ho un mucchio di coppie nome-genitore, che vorrei trasformare nel minor numero possibile di strutture ad albero gerarchiche. Quindi, ad esempio, questi potrebbero essere gli abbinamenti:

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

Che deve essere trasformato in (a) albero / i gerarchico / i:

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

Il risultato finale che voglio è un insieme nidificato di <ul>elementi, ognuno <li>contenente il nome del bambino.

Non ci sono incongruenze negli accoppiamenti (il figlio è il proprio genitore, il genitore è il figlio del bambino, ecc.), Quindi è possibile fare un po 'di ottimizzazioni.

Come, in PHP, potrei passare da un array contenente coppie figlio => genitore, a un insieme di nidificati <ul>?

Ho la sensazione che sia coinvolta la ricorsione, ma non sono abbastanza sveglio per pensarci bene.

Risposte:


129

Ciò richiede una funzione ricorsiva di base per analizzare le coppie figlio / genitore in una struttura ad albero e un'altra funzione ricorsiva per stamparla. Solo una funzione sarebbe sufficiente, ma eccone due per chiarezza (una funzione combinata può essere trovata alla fine di questa risposta).

Inizializza prima l'array di coppie figlio / genitore:

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

Quindi la funzione che analizza quell'array in una struttura ad albero gerarchica:

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;    
}

E una funzione che attraversa quell'albero per stampare un elenco non ordinato:

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>';
    }
}

E l'utilizzo effettivo:

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

Ecco i contenuti di $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
                            )
                        )
                    )
                )
            )
        )
    )
)

Se vuoi un po 'più di efficienza, puoi combinare queste funzioni in una e ridurre il numero di iterazioni effettuate:

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>';
    }
}

Risparmierai solo 8 iterazioni su un set di dati piccolo come questo, ma su set più grandi potrebbe fare la differenza.


2
Tatu. Come posso cambiare la funzione printTree per non echeggiare direttamente l'html dell'albero ma salvare tutto l'html di output in una variabile e restituirlo? grazie
Enrique

Salve, penso che la dichiarazione della funzione debba essere parseAndPrintTree ($ tree, $ root = null) e la chiamata ricorsiva deve essere parseAndPrintTree ($ child, $ tree); Cordiali saluti
Razor7

55

Ancora un'altra funzione per creare un albero (nessuna ricorsione coinvolta, utilizza invece riferimenti):

$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;
}

Restituisce un array gerarchico come questo:

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

Che può essere facilmente stampato come un elenco HTML utilizzando la funzione ricorsiva.


+1 - Molto intelligente. Anche se trovo la soluzione ricorsiva più logica. Ma preferisco il formato di output della tua funzione.
Eric

@ Eric più logico? Mi permetto di dissentire. Non c'è niente di "logico" nella ricorsione; c'è OTOH un grave sovraccarico cognitivo nell'analisi di funzioni / chiamate ricorsive. Se non c'è un'allocazione esplicita dello stack, prenderei l'iterazione sulla ricorsione ogni giorno.


29

Un altro modo più semplificato per convertire la struttura piatta $treein una gerarchia. È necessario un solo array temporaneo per esporlo:

// 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);

Questo è tutto per ottenere la gerarchia in un array multidimensionale:

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
)

L'output è meno banale se si desidera evitare la ricorsione (può essere un peso con strutture grandi).

Ho sempre voluto risolvere il "dilemma" UL / LI per l'output di un array. Il dilemma è che ogni elemento non sa se i bambini seguiranno o quanti elementi precedenti devono essere chiusi. In un'altra risposta l'ho già risolto usando un RecursiveIteratorIteratore cercando getDepth()e altre meta-informazioni fornite dai miei scritti Iterator: Ottenere un modello di insieme annidato in un <ul>sottoalbero "chiuso" ma nascosto . Quella risposta mostra anche che con gli iteratori sei abbastanza flessibile.

Tuttavia quello era un elenco preordinato, quindi non sarebbe adatto per il tuo esempio. Inoltre ho sempre voluto risolvere questo problema per una sorta di struttura ad albero standard e HTML <ul>e<li> elementi.

Il concetto di base che mi è venuto in mente è il seguente:

  1. TreeNode- Astratta ogni elemento in un TreeNodetipo semplice che può fornire il suo valore (ad esempio Name) e se ha o meno figli.
  2. TreeNodesIterator- A RecursiveIteratorche è in grado di iterare su un insieme (array) di questi TreeNodes. È abbastanza semplice in quanto il TreeNodetipo sa già se ha figli e quali.
  3. RecursiveListIterator- A RecursiveIteratorIteratorche ha tutti gli eventi necessari quando itera ricorsivamente su qualsiasi tipo di RecursiveIterator:
    • beginIteration/ endIteration- Inizio e fine dell'elenco principale.
    • beginElement/ endElement- Inizio e fine di ogni elemento.
    • beginChildren/ endChildren- Inizio e fine di ogni elenco figli. Questo RecursiveListIteratorfornisce solo questi eventi in una forma di chiamate di funzione. le liste figli, come è tipico per le <ul><li>liste, vengono aperte e chiuse all'interno del suo <li>elemento genitore . Pertanto l' endElementevento viene generato dopo l' endChildrenevento corrispondente. Questo potrebbe essere modificato o reso configurabile per ampliare l'uso di questa classe. Gli eventi vengono quindi distribuiti come chiamate di funzione a un oggetto decoratore, per tenere le cose separate.
  4. ListDecorator- Una classe "decoratore" che è solo un destinatario degli eventi di RecursiveListIterator.

Comincio con la logica di uscita principale. Preso l' $treearray ora gerarchico , il codice finale è simile al seguente:

$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());
}

Guardare prima di lasciare nel ListDecoratoravvolge che semplicemente le <ul>e <li>elementi e sta decidendo su come la struttura della lista è in uscita:

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

Il costruttore prende l'iteratore della lista su cui sta lavorando. insetè solo una funzione di aiuto per un buon rientro dell'output. Il resto sono solo le funzioni di output per ogni evento:

    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());
    }
}

Con queste funzioni di output in mente, questo è di nuovo il wrap-up / loop dell'output principale, lo esamino passo dopo passo:

$root = new TreeNode($tree);

Crea la radice TreeNodeche verrà utilizzata per avviare l'iterazione su:

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

Questo TreeNodesIteratorè un RecursiveIteratorche abilita l'iterazione ricorsiva sul singolo $rootnodo. Viene passato come un array perché quella classe ha bisogno di qualcosa su cui iterare e consente il riutilizzo con un insieme di figli che è anche un array di TreeNodeelementi.

$rit = new RecursiveListIterator($it);

Questo RecursiveListIteratorè un RecursiveIteratorIteratorche fornisce i suddetti eventi. Per utilizzarlo, è ListDecoratornecessario fornire solo a (la classe sopra) e assegnare addDecorator:

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

Quindi tutto è impostato su di foreachesso e genera ogni nodo:

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

Come mostra questo esempio, l'intera logica di output è incapsulata nella ListDecoratorclasse e in questo singoloforeach . L'intero attraversamento ricorsivo è stato completamente incapsulato in iteratori ricorsivi SPL che hanno fornito una procedura in pila, il che significa che internamente non vengono eseguite chiamate di funzione di ricorsione.

L'evento basato ListDecoratorconsente di modificare l'output in modo specifico e di fornire più tipi di elenchi per la stessa struttura dati. È anche possibile modificare l'input poiché i dati dell'array sono stati incapsulatiTreeNode .

L'esempio di codice completo:

<?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>

Demo (variante PHP 5.2)

Una possibile variante sarebbe un iteratore che itera su qualsiasi RecursiveIteratore fornisce un'iterazione su tutti gli eventi che possono verificarsi. Un interruttore / caso all'interno del ciclo foreach potrebbe quindi gestire gli eventi.

Relazionato:


3
Per quanto "ben progettata" sia questa soluzione - in che modo è esattamente "un modo più semplificato" rispetto agli esempi precedenti - Sembra proprio una soluzione troppo ingegnerizzata allo stesso problema
Andre

@Andre: dal grado di incapsulamento IIRC. In un'altra risposta correlata ho un frammento di codice completamente non incapsulato che è molto più piccolo e potrebbe quindi essere "più semplificato" a seconda del POV.
hakre

@hakre Come posso modificare la classe "ListDecorator" per aggiungere 'id' a LI, che viene recuperato dall'array dell'albero?
Gangesh

1
@Gangesh: più facilmente con un nodo vistor. ^^ Scherzando un po ', è semplice estendere il decoratore e modificare beginElement (), ottenere l'iteratore interno (vedere il metodo inset () per un esempio) e il lavoro con l'attributo id.
hakre

@hakre Thanks. Lo proverò.
Gangesh,

8

Bene, prima trasformerei l'array lineare di coppie chiave-valore in un array gerarchico

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];
}

Questo può convertire un array piatto con parent_id e id in uno gerarchico:

$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(),
                ),
            ),
        ),
    ),
);

Quindi, crea semplicemente una funzione di rendering:

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;
}

5

Mentre Alexander-Konstantinov potrebbe non sembrare così facile da leggere all'inizio, è sia geniale che esponenzialmente migliore in termini di prestazioni, questa avrebbe dovuto essere votata come la migliore risposta.

Grazie amico, ho fatto un benchmark in tuo onore per confrontare le 2 soluzioni presentate in questo post.

Avevo un albero piatto @ 250k con 6 livelli che dovevo convertire e stavo cercando un modo migliore per farlo ed evitare iterazioni ricorsive.

Ricorsione vs Riferimento:

// 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;

L'output parla da solo:

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

2

Bene, per analizzare UL e LIs, sarebbe qualcosa del tipo:

$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>';
}

Ma mi piacerebbe vedere una soluzione che non richiede di scorrere l'array così spesso ...


2

Ecco cosa mi è venuto in mente:

$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
      }
    }

uscite:

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

            [E] => Array
                (
                    [A] => A
                    [C] => Array
                        (
                            [B] => B
                        )    
                )    
        )    
)

1

Relazione padre-figlio nidificata Array
Recupera tutto il record dal database e crea un array nidificato.

$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;
}

Stampa i dati delle categorie e delle sottocategorie in formato 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
    ];
}

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 ($ albero);

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>";
        }
    }

0

Vecchia domanda, ma anch'io dovevo farlo e gli esempi con ricorsione mi hanno fatto venire il mal di testa. Nel mio database abbiamo una locationstabella, che era una loca_idPK (Child) e autoreferenzialeloca_parent_id (Parent).

Lo scopo è rappresentare questa struttura in HTML. La semplice query può di couyrse restituire i dati è un certo ordine fisso ma non ho trovato abbastanza bene per visualizzare tali dati in modo naturale. Quello che volevo davvero era la gestione della camminata sugli alberi OracleLEVEL per aiutare con la visualizzazione.

Ho deciso di utilizzare l'idea di un "percorso" per identificare in modo univoco ogni voce. Per esempio:

L'ordinamento dell'array in base al percorso dovrebbe semplificare l'elaborazione per una visualizzazione significativa.

Mi rendo conto che l'uso di array e ordinamenti associativi è un imbroglio in quanto nasconde la complessità ricorsiva delle operazioni, ma a me sembra più semplice:

<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>

-1

Come creare un menu e una vista ad albero dinamici

Passaggio 1: per prima cosa creeremo una tabella treeview nel database mysql. questa tabella contiene quattro column.id è l'id dell'attività e il nome è il nome dell'attività.

-
-- 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');

Passaggio 2: metodo ricorsivo della visualizzazione ad albero che ho creato sotto il metodo createTreeView () dell'albero che chiama ricorsivo se l'ID dell'attività corrente è maggiore dell'ID dell'attività precedente.

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> ";

}

Passaggio 3: creare un file indice per mostrare la visualizzazione ad albero. Questo è il file principale dell'esempio di visualizzazione ad albero, qui chiameremo il metodo createTreeView () con i parametri richiesti.

 <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>

Passo 4: Crea file CSS style.css Qui scriveremo tutta la classe relativa a CSS, attualmente sto usando l'elenco degli ordini per creare la visualizzazione ad albero. puoi anche cambiare il percorso dell'immagine qui.

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 */ }

Più dettagli

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.