Bir dizi ebeveyn-alt öğe ilişkisi hiyerarşik bir ağaca dönüştürülsün mü?


101

Mümkün olduğunca az hiyerarşik ağaç yapısına dönüştürmek istediğim bir grup ad-ebeveyn-isim çiftim var. Örneğin, bunlar şu eşlemeler olabilir:

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

Hangisinin (a) hiyerarşik ağaç (lar) a dönüştürülmesi gerekenler:

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

İstediğim sonuç, <ul>her <li>biri çocuğun adını içeren iç içe geçmiş öğeler kümesidir .

Eşleştirmelerde tutarsızlık yoktur (çocuk kendi ebeveynidir, ebeveyn çocuğun çocuğudur vb.), Bu nedenle bir dizi optimizasyon yapılabilir.

PHP'de, child => parent çiftlerini içeren bir diziden bir İç içe geçmiş dizilere <ul>nasıl geçebilirim ?

Yinelemenin işin içinde olduğu hissine kapılıyorum, ama bunu düşünecek kadar uyanık değilim.

Yanıtlar:


129

Bu, alt / üst çiftleri bir ağaç yapısına ayrıştırmak için çok basit bir özyinelemeli işlev ve bunu yazdırmak için başka bir özyinelemeli işlev gerektirir. Sadece bir fonksiyon yeterli olabilir, ancak burada iki tane açıklık için (bu cevabın sonunda birleşik bir fonksiyon bulunabilir).

İlk önce alt / üst çiftlerin dizisini başlatın:

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

Ardından, bu diziyi hiyerarşik bir ağaç yapısına ayrıştıran işlev:

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

Ve sırasız bir liste yazdırmak için bu ağaçtan geçen bir işlev:

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

Ve gerçek kullanım:

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

İşte içeriği $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
                            )
                        )
                    )
                )
            )
        )
    )
)

Biraz daha fazla verimlilik istiyorsanız, bu işlevleri tek bir işlevde birleştirebilir ve yapılan yineleme sayısını azaltabilirsiniz:

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

Bu kadar küçük bir veri kümesinde yalnızca 8 yineleme kaydedersiniz, ancak daha büyük kümelerde bir fark yaratabilir.


2
Tatu. PrintTree işlevini ağacın html'sini doğrudan yansıtmayacak, ancak tüm çıktı html'sini bir değişkene kaydedip geri döndürecek şekilde nasıl değiştirebilirim? teşekkürler
Enrique

Merhaba, sanırım işlev bildirimi parseAndPrintTree ($ tree, $ root = null) ve özyinelemeli çağrı parseAndPrintTree ($ child, $ tree) olmalı; Saygılarımızla
jilet7

55

Yine Bir Ağaç Yapmak İçin Başka Bir İşlev (özyineleme içermez, bunun yerine referanslar kullanır):

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

Bunun gibi bir hiyerarşik dizi döndürür:

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

Özyinelemeli işlev kullanılarak HTML listesi olarak kolayca yazdırılabilir.


+1 - Çok zekice. Yinelemeli çözümü daha mantıklı bulsam da. Ama işlevinizin çıktı biçimini tercih ederim.
Eric

@Eric daha mantıklı? Naçizane size katılmıyorum. Özyinelemede 'mantıklı' hiçbir şey yoktur; OTOH, özyinelemeli işlevleri / çağrıları çözümlemede ciddi bir bilişsel ek yük var. Açık bir yığın tahsisi yoksa, her gün yineleme yerine yinelemeyi alırım.


29

Düz yapıyı $treehiyerarşiye dönüştürmenin daha basitleştirilmiş başka bir yolu . Göstermek için yalnızca bir geçici dizi gereklidir:

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

Hepsi hiyerarşiyi çok boyutlu bir diziye sokmak için:

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
)

Özyinelemeden kaçınmak istiyorsanız çıktı daha az önemsizdir (büyük yapılarda bir yük olabilir).

Her zaman bir dizi çıktısını almak için UL / LI "ikilemini" çözmek istemişimdir. Buradaki ikilem, her maddenin çocukların takip edip etmeyeceğini veya önceki öğelerin kaçının kapatılması gerektiğini bilmemesidir. Başka bir cevapta, bunu a RecursiveIteratorIteratorve arama getDepth()ve kendi yazdığım diğer meta bilgileri kullanarak çözdüm Iterator: İç içe geçmiş küme modelini, <ul>ancak "kapalı" alt ağaçların içine almak . Bu cevap , yineleyicilerle oldukça esnek olduğunuzu da gösteriyor.

Ancak bu önceden sıralanmış bir listeydi, bu nedenle sizin örneğiniz için uygun olmayacaktır. Ek olarak, bunu her zaman bir tür standart ağaç yapısı ve HTML'ler <ul>ve <li>öğeler için çözmek istedim .

Bulduğum temel kavram şudur:

  1. TreeNode- Her bir öğeyi, TreeNodedeğerini sağlayabilecek basit bir türe (örneğin Name) ve çocukları olup olmadığına göre özetler.
  2. TreeNodesIterator- RecursiveIteratorBunlardan oluşan bir dizi (dizi) üzerinde yineleme yapabilen bir TreeNodes. Bu oldukça basit, çünkü TreeNodetipin çocuğu olup olmadığını ve hangilerinin çocuk sahibi olduğunu zaten biliyor.
  3. RecursiveListIterator- RecursiveIteratorIteratorHerhangi bir tür üzerinde yinelemeli olarak yinelendiğinde gereken tüm olayları içeren bir RecursiveIterator:
    • beginIteration/ endIteration- Ana listenin başlangıcı ve sonu.
    • beginElement/ endElement- Her öğenin başlangıcı ve bitişi.
    • beginChildren/ endChildren- Her çocuk listesinin başlangıcı ve bitişi. Bu RecursiveListIterator, bu olayları yalnızca bir işlev çağrısı biçiminde sağlar. Listeler için tipik olduğu gibi alt <ul><li>listeler, ana <li>öğesi içinde açılır ve kapanır . Bu nedenle endElementolay, ilgili olaydan sonra tetiklenir endChildren. Bu, bu sınıfın kullanımını genişletmek için değiştirilebilir veya yapılandırılabilir hale getirilebilir. Olaylar, daha sonra işleri birbirinden ayırmak için bir dekoratör nesnesine işlev çağrıları olarak dağıtılır.
  4. ListDecorator- Olayların sadece alıcısı olan bir "dekoratör" sınıfı RecursiveListIterator.

Ana çıktı mantığıyla başlıyorum. Şimdi hiyerarşik $treedizi alındığında, son kod aşağıdaki gibi görünür:

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

Öncelikle ListDecorator, basitçe <ul>ve <li>öğelerini saran ve liste yapısının nasıl çıktılacağına karar verelim :

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

Yapıcı, üzerinde çalıştığı liste yineleyicisini alır. insetsadece çıktının hoş girintisi için yardımcı bir işlevdir. Geri kalanlar, her olay için yalnızca çıktı işlevleridir:

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

Bu çıktı işlevlerini göz önünde bulundurarak, bu yine ana çıktı özetleme / döngüdür, adım adım ilerledim:

$root = new TreeNode($tree);

TreeNodeYinelemeyi başlatmak için kullanılacak olan kökü oluşturun :

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

Bu TreeNodesIterator, RecursiveIteratortek $rootdüğüm üzerinde yinelemeli yinelemeyi etkinleştiren bir yöntemdir . Bir dizi olarak aktarılır çünkü bu sınıfın yinelenecek bir şeye ihtiyacı vardır ve aynı zamanda bir TreeNodeöğe dizisi olan bir dizi çocukla yeniden kullanıma izin verir .

$rit = new RecursiveListIterator($it);

Bu RecursiveListIterator, RecursiveIteratorIteratorsöz konusu olayları sağlayan bir şeydir. Bundan yararlanmak için, yalnızca ListDecorator(yukarıdaki sınıf) sağlanmalı ve aşağıdakilerle atanmalıdır addDecorator:

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

Sonra her şey hemen foreachüzerine kurulur ve her bir düğümü çıktı olarak verir:

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

Bu örnekte gösterildiği gibi, tüm çıktı mantığı ListDecoratorsınıf ve bu tek içinde kapsüllenmiştir foreach. Tüm özyinelemeli geçiş, yığınlanmış bir prosedür sağlayan SPL özyinelemeli yineleyicilerde tamamen kapsüllenmiştir, bu, dahili olarak hiçbir özyineleme işlevi çağrısının yapılmadığı anlamına gelir.

Olay tabanlı ListDecorator, çıktıyı özel olarak değiştirmenize ve aynı veri yapısı için birden çok liste türü sağlamanıza olanak tanır. Dizi verilerinin içine kapsüllendiği için girişi değiştirmek bile mümkündür TreeNode.

Tam kod örneği:

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

Çıkış:

<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 varyantı)

Olası bir varyant, herhangi birinin üzerinde yineleyen RecursiveIteratorve meydana gelebilecek tüm olaylar üzerinde bir yineleme sağlayan bir yineleyici olabilir. Foreach döngüsünün içindeki bir anahtar / durum olaylarla ilgilenebilir.

İlişkili:


3
Bu çözüm ne kadar "iyi tasarlanmış" olsa da - önceki örneklerden tam olarak nasıl "daha basitleştirilmiş bir yöntemdir" - Aynı soruna aşırı tasarlanmış bir çözüm gibi görünüyor
Andre

@Andre: IIRC kapsülleme derecesine göre. Başka bir ilgili cevapta, tamamen kapsüllenmemiş bir kod parçam var, bu çok daha küçük ve bu nedenle POV'a bağlı olarak "daha basitleştirilmiş" olabilir.
hakre

@hakre "ListDecorator" sınıfını, ağaç dizisinden getirilen LI'ye 'id' eklemek için nasıl değiştirebilirim?
Gangesh

1
@Gangesh: Bir node vistor ile en kolay şekilde. ^^ Biraz şaka yapmak, doğrudan dekoratörü genişletmek ve beginElement () 'i düzenlemek, iç yineleyiciyi (bir örnek için inset () yöntemine bakın) ve id niteliği ile çalışmayı almaktır.
hakre

@hakre Teşekkürler. Bunu deneyeceğim.
Gangesh

8

Öncelikle, düz anahtar-değer çiftleri dizisini hiyerarşik bir diziye dönüştürürdüm

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

Bu, parent_id ve id ile düz bir diziyi hiyerarşik bir diziye dönüştürebilir:

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

Ardından, bir oluşturma işlevi oluşturun:

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

İken Alexander-Konstantinov 'ın çözüm ilk bakışta okunması kolay olarak görünmeyebilir o deha ve katlanarak daha iyi performans açısından bu olmalıydı iyi yanıt olarak olarak hem de.

Teşekkürler dostum, bu yazıda sunulan 2 çözümü karşılaştırmak için şerefinize bir değerlendirme yaptım.

Dönüştürmem gereken 6 seviyeli @ 250k düz bir ağacım vardı ve bunu yapmanın ve yinelemeli yinelemelerden kaçınmanın daha iyi bir yolunu arıyordum.

Özyineleme ve Referans:

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

Çıktı kendi adına konuşuyor:

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

2

UL'leri ve LI'leri ayrıştırmak şöyle bir şey olacaktır:

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

Ancak diziyi çok sık yinelemenizi gerektirmeyen bir çözüm görmeyi çok isterim ...


2

İşte bulduğum şey:

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

çıktılar:

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

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

1

Üst-alt ilişkisi yuvalanmış Dizi
Veritabanından tüm kayıtları alır ve yuvalanmış dizi oluşturur.

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

Kategorileri ve Alt kategorileri json Formatında yazdırın

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 ($ ağaç);

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

Eski soru, ama ben de bunu yapmak zorundaydım ve özyineli örnekler başımı ağrıttı. Veritabanımda locations, loca_idPK (Çocuk) ve kendine referans veren loca_parent_id(Ebeveyn) bir tablomuz var .

Amaç, bu yapıyı HTML'de temsil etmektir. Verileri döndürmenin basit sorgu kutusu sabit bir sıradır, ancak bu tür verileri doğal bir şekilde görüntülemek için yeterince iyi bulamadım. Gerçekten istediğim şey LEVEL, görüntülemeye yardımcı olmak için Oracle ağaç yürüyüşü yapmaktı .

Her girişi benzersiz bir şekilde tanımlamak için bir 'yol' fikrini kullanmaya karar verdim. Örneğin:

Diziyi yola göre sıralamak, anlamlı görüntüleme için işlemeyi kolaylaştırmalıdır.

İlişkilendirmeli dizilerin ve sıralamaların kullanımının, işlemlerin yinelemeli karmaşıklığını gizlediği için hile yapmak olduğunu anlıyorum, ancak bana göre bu daha basit görünüyor:

<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

Dinamik Ağaç Görünümü ve Menüsü Nasıl Oluşturulur

Adım 1: Öncelikle mysql veritabanında ağaç görünüm tablosu oluşturacağız. bu tablo dört sütun içerir. id, görev kimliği ve adı görev adıdır.

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

Adım 2: Ağaç görünümü özyinelemeli yöntem Ağacın altında createTreeView () yöntemini oluşturdum ve mevcut görev kimliği önceki görev kimliğinden büyükse özyinelemeli çağırıyor.

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

}

Adım 3: Ağaç görünümünü göstermek için dizin dosyası oluşturun. Bu, ağaç görünümü örneğinin ana dosyasıdır, burada gerekli parametrelerle createTreeView () yöntemini çağıracağız.

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

Adım 4: CSS dosyası oluşturma style.css Burada css ile ilgili tüm sınıfları yazacağız, şu anda ağaç görünümü oluşturmak için sipariş listesini kullanıyorum. Ayrıca burada resim yolunu da değiştirebilirsiniz.

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

Daha fazla detay

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.