Создание дерева из плоского массива в PHP

Я просмотрел интернет и не совсем нашел то, что искал. У меня есть плоский массив с каждым элементом, содержащим "id" и "parent_id". Каждый элемент будет иметь только один родительский элемент, но может иметь несколько дочерних элементов. Если parent_id = 0, он считается элементом уровня корня. Я пытаюсь получить свой плоский массив в дерево. Другие образцы, которые я нашел, только копируют элемент в родительский, но оригинал все еще существует.

ИЗМЕНИТЬ

Каждый элемент стартового массива считывается из отдельного файла XML. Сам файл будет иметь значение "0" в качестве значения parent_id, если у него нет родителя. Клавиши на самом деле являются строками.

Прошу прощения за путаницу раньше. Надеюсь, это более понятно:

/EDIT

Мой начальный массив:

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

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

    [_321_] => Array
        (
            [id] => _321_
            [parent_id] => _320_
        )

    [_322_] => Array
        (
            [id] => _322_
            [parent_id] => _321_
        )

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

    [_324_] => Array
        (
            [id] => _324_
            [parent_id] => _323_
        )

    [_325_] => Array
        (
            [id] => _325_
            [parent_id] => _320_
        )
)

Результирующий массив после дерева:

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

    [_320_] => Array
        (
            [id] => _320_
            [parent_id] => 0
            [children] => Array
                (
                    [_321_] => Array
                        (
                            [id] => _321_
                            [parent_id] => _320_
                            [children] => Array
                                (
                                    [_322_] => Array
                                        (
                                            [id] => _322_
                                            [parent_id] => _321_
                                        )
                                )
                        )
                    [_325_] => Array
                        (
                            [id] => _325_
                            [parent_id] => _320_
                        )
                )
    [_323_] => Array
        (
            [id] => _323_
            [parent_id] => 0
            [children] => Array
                (
                    [_324_] => Array
                        (
                            [id] => _324_
                            [parent_id] => _323_
                        )
                )
        )

Любая помощь/руководство приветствуются!

Некоторый код, который у меня есть до сих пор:

        function buildTree(array &$elements, $parentId = 0) {
        $branch = array();

        foreach ($elements as $element) {
            if ($element['parent_id'] == $parentId) {
                $children = $this->buildTree($elements, $element['id']);
                if ($children) {
                    $element['children'] = $children;
                }
                $branch[] = $element;
            }
        }

        return $branch;
    }

Ответ 1

Ты забыл unset() там, где есть.

function buildTree(array &$elements, $parentId = 0) {
    $branch = array();

    foreach ($elements as $element) {
        if ($element['parent_id'] == $parentId) {
            $children = buildTree($elements, $element['id']);
            if ($children) {
                $element['children'] = $children;
            }
            $branch[$element['id']] = $element;
            unset($elements[$element['id']]);
        }
    }
    return $branch;
}

Ответ 2

Решение ImmortalFirefly работает, однако, как указывает mrded, оно не спасает первых родителей без детей. Я отредактировал эту функцию, чтобы исправить эту проблему:

function buildTree(array &$elements, $parentId = 0) {

    $branch = array();

    foreach ($elements as &$element) {

        if ($element['parent_id'] == $parentId) {
            $children = buildTree($elements, $element['id']);
            if ($children) {
                $element['children'] = $children;
            }
            $branch[$element['id']] = $element;
            unset($element);
        }
    }
    return $branch;
}

Ответ 3

Это работает для меня:

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

Ответ 4

Я вижу логику, за исключением этого в результате:

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

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

IMHO, parent_id = o, не должен [1] быть дочерним элементом [0] здесь?

В любом случае, ссылки на спасение:

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

Поскольку вы злоупотребляли 0 как "магическое" число как root, так и существующий id, теперь мы имеем рекурсию в ветке id = 0. Добавление if($item['parent_id']!=$item['id']) до $tree[$item['parent_id']]['children'][] = &$tree[$item['id']]; может помешать этому, но это не так.

Ответ 5

Возможно, что исходный массив немного отличается, вы можете использовать эту функцию (parent_id, id, title):

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

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

echo '<select>';
render_select();
echo '</select>';

Ответ 6

Хотя это старый вопрос, я собираюсь опубликовать свой ответ здесь:

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

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

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

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

array_column - это PHP 5.5, но вы можете сделать свой собственный легко.

Ответ 7

Вы хотите смотреть на хранение и загрузку иерархических данных в MySQL, поскольку я должен решить несколько проблем. Я предполагаю, что первый массив представляет данные, полученные непосредственно из базы данных?

Похоже, вы пытаетесь использовать модель смежности для организации своих данных в структуре иерархии. Есть и другие способы достижения этой цели с помощью гнездования. Если вы не берете эти данные из базы данных, это может быть не так полезно.

Эта ссылка должна помочь вам: http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/

Ответ 8

Код SteveEdson работает нормально, за исключением случая, когда родительский элемент не существует в исходной структуре данных. Вот мое исправление для этого (однако, оно удаляет "parent_id" из элементов, которые могут или не могут быть приемлемы):

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

Ответ 9

Вот мое решение, идеально работает, если предположить, что верхний уровень parent_id = 0:

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

Ответ 10

Это мое решение, копирование и оптимизация других решений.

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

Ответ 11

Чистое, короткое и свободное от балласта. Массив массивов к дереву:

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

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

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

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

Ответ 12

Я придумал решение, похожее на @eugen-rieck, и хотел поделиться им. Но я назвал $branches своим массивом индексов.

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

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

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

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

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

Ответ 13

Прошло много времени с момента вопроса, но я хочу поделиться своей функцией, поскольку я считаю, что она супер хороша для производительности, намного лучше, чем рекурсивная, даже больше для памяти, и это более понятно, чем другие подходы:

function buildTree(array &$elements, int $parentId = 0){
    $categories = array();
    $pointers   = array();
    foreach($elements as $key => $val)
    {
        $elements[$key]['id']       = $key;//Added due to author example

        $category                   = intval($key);//Using $key due to author first id being 0 while key is 319
        $parent                     = intval($val['parent_id']) === 0 ? $category : intval($val['parent_id']);

        if(isset($pointers[$parent]))
        {
            $pointers[$parent][$category]   = $val;
            $pointers[$category]            =& $pointers[$parent][$category];
        }else
        {
            $categories[$category]  = $val;
            $pointers[$category]    =& $categories[$category];
        }

    }
    return $categories;
}