Eine Reihe von Eltern-Kind-Beziehungen in einen hierarchischen Baum konvertieren?


100

Ich habe eine Reihe von Name-Eltern-Namen-Paaren, die ich in möglichst wenige heirarchische Baumstrukturen verwandeln möchte. Dies könnten zum Beispiel die Paarungen sein:

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

Was muss in (a) heirarchische Bäume umgewandelt werden:

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

Das gewünschte Endergebnis ist eine verschachtelte Gruppe von <ul>Elementen, von denen jedes <li>den Namen des Kindes enthält.

Es gibt keine Inkonsistenzen in den Paarungen (Kind ist das eigene Elternteil, Elternteil ist das Kind des Kindes usw.), sodass wahrscheinlich eine Reihe von Optimierungen vorgenommen werden können.

Wie würde ich in PHP von einem Array mit untergeordneten => übergeordneten Paaren zu einer Reihe verschachtelter <ul>s wechseln ?

Ich habe das Gefühl, dass es sich um eine Rekursion handelt, aber ich bin nicht wach genug, um darüber nachzudenken.

Antworten:


129

Dies erfordert eine sehr einfache rekursive Funktion zum Parsen der untergeordneten / übergeordneten Paare in eine Baumstruktur und eine weitere rekursive Funktion zum Ausdrucken. Nur eine Funktion würde ausreichen, aber hier sind zwei zur Verdeutlichung (eine kombinierte Funktion finden Sie am Ende dieser Antwort).

Initialisieren Sie zuerst das Array der Kind / Eltern-Paare:

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

Dann die Funktion, die dieses Array in eine hierarchische Baumstruktur analysiert:

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

Und eine Funktion, die diesen Baum durchläuft, um eine ungeordnete Liste auszudrucken:

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

Und die tatsächliche Verwendung:

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

Hier ist der Inhalt von $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
                            )
                        )
                    )
                )
            )
        )
    )
)

Wenn Sie ein bisschen mehr Effizienz wünschen, können Sie diese Funktionen zu einer kombinieren und die Anzahl der durchgeführten Iterationen reduzieren:

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

Sie speichern nur 8 Iterationen in einem so kleinen Datensatz, aber in größeren Sätzen kann dies einen Unterschied machen.


2
Tatu. Wie kann ich die printTree-Funktion so ändern, dass das HTML des Baums nicht direkt wiedergegeben wird, sondern das gesamte Ausgabe-HTML in einer Variablen gespeichert und zurückgegeben wird? danke
Enrique

Hallo, ich denke, die Funktionsdeklaration muss parseAndPrintTree ($ tree, $ root = null) sein und der rekursive Aufruf muss parseAndPrintTree ($ child, $ tree) sein.
Viele

55

Noch eine weitere Funktion zum Erstellen eines Baums (keine Rekursion erforderlich, verwendet stattdessen Referenzen):

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

Gibt ein hierarchisches Array wie dieses zurück:

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

Die mit rekursiver Funktion einfach als HTML-Liste gedruckt werden kann.


+1 - Sehr klug. Obwohl ich die rekursive Lösung logischer finde. Aber ich bevorzuge das Ausgabeformat Ihrer Funktion.
Eric

@ Eric logischer? Ich bin anderer Ansicht. Es gibt nichts 'Logisches' in der Rekursion; OTOH verursacht einen erheblichen kognitiven Aufwand beim Parsen rekursiver Funktionen / Aufrufe. Wenn es keine explizite Stapelzuweisung gibt, würde ich jeden Tag eine Iteration über die Rekursion durchführen.


29

Eine andere, vereinfachte Möglichkeit, die flache Struktur $treein eine Hierarchie umzuwandeln . Es wird nur ein temporäres Array benötigt, um es verfügbar zu machen:

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

Das ist alles, um die Hierarchie in ein mehrdimensionales Array zu bringen:

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
)

Die Ausgabe ist weniger trivial, wenn Sie eine Rekursion vermeiden möchten (kann bei großen Strukturen eine Belastung sein).

Ich wollte immer das UL / LI- "Dilemma" für die Ausgabe eines Arrays lösen. Das Dilemma besteht darin, dass jedes Element nicht weiß, ob Kinder nachverfolgen oder wie viele vorhergehende Elemente geschlossen werden müssen. In einer anderen Antwort habe ich das bereits gelöst, indem ich eine RecursiveIteratorIteratorund nach getDepth()anderen Metainformationen gesucht und gesucht habe , die ich selbst geschrieben Iteratorhabe: Verschachteltes Mengenmodell in einen <ul>aber ausgeblendeten „geschlossenen“ Teilbaum zu bringen . Diese Antwort zeigt auch, dass Sie mit Iteratoren ziemlich flexibel sind.

Dies war jedoch eine vorsortierte Liste und daher für Ihr Beispiel nicht geeignet. Außerdem wollte ich dies immer für eine Art Standardbaumstruktur sowie HTMLs <ul>und <li>Elemente lösen .

Das Grundkonzept, das ich mir ausgedacht habe, ist das folgende:

  1. TreeNode- Abstrakt jedes Element in einen einfachen TreeNodeTyp, der seinen Wert liefern kann (z. B. Name) und ob es untergeordnete Elemente hat oder nicht.
  2. TreeNodesIterator- A RecursiveIterator, das über eine Menge (Array) von diesen iterieren kann TreeNodes. Das ist ziemlich einfach, da der TreeNodeTyp bereits weiß, ob und welche Kinder er hat.
  3. RecursiveListIterator- A RecursiveIteratorIterator, das alle Ereignisse enthält, die erforderlich sind, wenn es rekursiv über eine der folgenden Arten iteriert RecursiveIterator:
    • beginIteration/ endIteration- Anfang und Ende der Hauptliste.
    • beginElement/ endElement- Anfang und Ende jedes Elements.
    • beginChildren/ endChildren- Anfang und Ende jeder Kinderliste. Dies RecursiveListIteratorstellt diese Ereignisse nur in Form von Funktionsaufrufen bereit. Untergeordnete Listen werden, wie es für <ul><li>Listen typisch ist , innerhalb des übergeordneten <li>Elements geöffnet und geschlossen . Daher wird das endElementEreignis nach dem entsprechenden endChildrenEreignis ausgelöst . Dies kann geändert oder konfigurierbar gemacht werden, um die Verwendung dieser Klasse zu erweitern. Die Ereignisse werden dann als Funktionsaufrufe an ein Dekorationsobjekt verteilt, um die Dinge auseinander zu halten.
  4. ListDecorator- Eine "Dekorateur" -Klasse, die nur ein Empfänger der Ereignisse von ist RecursiveListIterator.

Ich beginne mit der Hauptausgangslogik. In dem jetzt hierarchischen $treeArray sieht der endgültige Code wie folgt aus:

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

Lassen Sie uns zunächst Blick in die , ListDecoratordie einfach wickelt die <ul>und <li>Elemente und ist die Entscheidung darüber , wie die Listenstruktur ausgegeben:

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

Der Konstruktor verwendet den Listeniterator, an dem er arbeitet. insetist nur eine Hilfsfunktion für eine schöne Einrückung der Ausgabe. Der Rest sind nur die Ausgabefunktionen für jedes Ereignis:

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

In Anbetracht dieser Ausgabefunktionen ist dies wieder die Hauptausgabe-Zusammenfassung / Schleife. Ich gehe sie Schritt für Schritt durch:

$root = new TreeNode($tree);

Erstellen Sie die Wurzel TreeNode, mit der die Iteration gestartet wird:

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

Dies TreeNodesIteratorist eine , RecursiveIteratordie rekursive Iteration über den einzelnen ermöglicht $rootKnoten. Es wird als Array übergeben, da diese Klasse etwas zum Durchlaufen benötigt und die Wiederverwendung mit einer Reihe von untergeordneten TreeNodeElementen ermöglicht, die auch ein Array von Elementen sind.

$rit = new RecursiveListIterator($it);

Dies RecursiveListIteratorist eine RecursiveIteratorIterator, die die genannten Ereignisse bereitstellt. Um davon Gebrauch zu machen, muss nur ein ListDecorator(die obige Klasse) bereitgestellt und zugewiesen werden mit addDecorator:

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

Dann wird alles so eingestellt, dass es direkt foreachdarüber liegt und jeden Knoten ausgibt:

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

Wie dieses Beispiel zeigt, ist die gesamte Ausgabelogik in der ListDecoratorKlasse und dieser einzelnen gekapselt foreach. Die gesamte rekursive Durchquerung wurde vollständig in rekursive SPL-Iteratoren eingekapselt, die eine gestapelte Prozedur bereitstellten. Dies bedeutet, dass intern keine Rekursionsfunktionsaufrufe ausgeführt werden.

Mit der ereignisbasierten Funktion ListDecoratorkönnen Sie die Ausgabe spezifisch ändern und mehrere Listentypen für dieselbe Datenstruktur bereitstellen. Es ist sogar möglich, die Eingabe zu ändern, wenn die Array-Daten eingekapselt wurden TreeNode.

Das vollständige Codebeispiel:

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

Ausbruch:

<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 (PHP 5.2 Variante)

Eine mögliche Variante wäre ein Iterator, der RecursiveIteratorüber alle Ereignisse iteriert und eine Iteration über alle Ereignisse bereitstellt, die auftreten können. Ein Schalter / Fall innerhalb der foreach-Schleife könnte sich dann mit den Ereignissen befassen.

Verbunden:


3
So "ausgereift" diese Lösung auch ist - wie genau ist sie "einfacher" als die vorherigen Beispiele - es scheint nur eine überentwickelte Lösung für dasselbe Problem zu sein
Andre

@Andre: Nach dem Grad der Einkapselung IIRC. In einer anderen verwandten Antwort habe ich ein vollständig nicht gekapseltes Codefragment, das viel kleiner ist und daher je nach POV "einfacher" sein könnte.
hakre

@hakre Wie kann ich die Klasse "ListDecorator" ändern, um 'L' zu LI hinzuzufügen, das aus dem Baumarray abgerufen wird?
Gangesh

1
@ Gangesh: Am einfachsten mit einem Node Vistor. ^^ Ein bisschen Spaß macht es einfach, den Dekorator zu erweitern und beginElement () zu bearbeiten, den inneren Iterator abzurufen (ein Beispiel finden Sie in der Methode inset ()) und die Arbeit mit dem Attribut id.
hakre

@akre Danke. Ich werde das versuchen.
Gangesh

8

Nun, zuerst würde ich das gerade Array von Schlüssel-Wert-Paaren in ein hierarchisches Array verwandeln

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

Dadurch kann ein flaches Array mit parent_id und id in ein hierarchisches Array konvertiert werden:

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

Erstellen Sie dann einfach eine Rendering-Funktion:

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

Während die Lösung von Alexander-Konstantinov auf den ersten Blick nicht so einfach zu lesen scheint, ist sie sowohl genial als auch exponentiell besser in Bezug auf die Leistung. Dies hätte als beste Antwort gewählt werden müssen.

Vielen Dank, Kumpel, ich habe zu Ihren Ehren einen Benchmark erstellt, um die beiden in diesem Beitrag vorgestellten Lösungen zu vergleichen.

Ich hatte einen @ 250k flachen Baum mit 6 Ebenen, die ich konvertieren musste, und suchte nach einem besseren Weg, dies zu tun und rekursive Iterationen zu vermeiden.

Rekursion vs Referenz:

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

Die Ausgabe spricht für sich:

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

2

Nun, um ULs und LIs zu analysieren, wäre es ungefähr so:

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

Aber ich würde gerne eine Lösung sehen, bei der Sie nicht so oft durch das Array iterieren müssen ...


2

Folgendes habe ich mir ausgedacht:

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

Ausgänge:

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

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

1

Verschachteltes Array der Eltern-Kind-Beziehung
Ruft den gesamten Datensatz aus der Datenbank ab und erstellt ein verschachteltes Array.

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

Drucken Sie Daten zu Kategorien und Unterkategorien im JSON-Format

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

0

Alte Frage, aber auch ich musste dies tun und die Beispiele mit Rekursion bereiteten mir Kopfschmerzen. In meiner Datenbank haben wir eine locationsTabelle, die eine loca_idPK (Kind) und eine selbstreferenzierende loca_parent_id(Eltern) war.

Ziel ist es, diese Struktur in HTML darzustellen. Die einfache Abfrage kann die Daten in fester Reihenfolge zurückgeben, aber ich fand sie nicht gut genug, um solche Daten auf natürliche Weise anzuzeigen. Was ich wirklich wollte, war die Bearbeitung von Oracle Tree Walks LEVEL, um die Anzeige zu erleichtern.

Ich entschied mich für die Idee eines "Pfades", um jeden Eintrag eindeutig zu identifizieren. Beispielsweise:

Das Sortieren des Arrays nach dem Pfad sollte die Verarbeitung für eine aussagekräftige Anzeige erleichtern.

Mir ist klar, dass die Verwendung von assoziativen Arrays und Sortierungen betrügt, da sie die rekursive Komplexität der Operationen verbirgt, aber für mich sieht dies einfacher aus:

<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

So erstellen Sie eine dynamische Baumansicht und ein Menü

Schritt 1: Zuerst erstellen wir eine Baumansichtstabelle in der MySQL-Datenbank. Diese Tabelle enthält vier Spalten. ID ist die Aufgaben-ID und Name ist der Aufgabenname.

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

Schritt 2: Rekursive Methode für die Baumansicht Ich habe unten die Methode createTreeView () für den Baum erstellt, die rekursiv aufruft, wenn die aktuelle Aufgaben-ID größer als die vorherige Aufgaben-ID ist.

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

}

Schritt 3: Erstellen Sie eine Indexdatei, um die Baumansicht anzuzeigen. Dies ist die Hauptdatei des Treeview-Beispiels. Hier rufen wir die Methode createTreeView () mit den erforderlichen Parametern auf.

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

Schritt 4: Erstellen der CSS-Datei style.css Hier schreiben wir alle CSS-bezogenen Klassen. Derzeit verwende ich die Bestellliste, um eine Baumansicht zu erstellen. Hier können Sie auch den Bildpfad ändern.

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

Mehr Details

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.