【问题标题】:Get deepest tree nodes for branches by given parent ids通过给定的父 ID 获取分支的最深树节点
【发布时间】:2018-01-24 21:28:55
【问题描述】:

假设我有这个 ids/categories 树:

1
    2
    3
        4
    5
6
    7
        8
        9
10
    11
        12
            13
            14

我需要一个函数来根据输入 id 返回所有叶子(端节点)。 但只有来自每个分支中给出的最深 id 的节点。

我不知道怎么解释,这里举几个例子:

f([1])      returns [2,4,5]
f([1,2])    returns [2]
f([1,3])    returns [4]
f([1,2,3])  returns [2,4]
f([3])      returns [4]
f([4])      returns [4]
f[1,6])     returns [2,4,5,8,9]
f[11])      returns [13, 14]
f[10])      returns [13, 14]

我正在使用的树的结构如下:

array(
    [category] => Object(Category)
    [children] => array(
        array(
            [category] => Object(Category)
            [children] => array(
                ...
            )
            [category] => Object(Category)
            [children] => array(
                ...
            )
    )

如果这样更容易的话,我也有可用的平面数组:

array(
    array(id, parent_id),
    array(id, parent_id),
    etc..
)

经过几个小时或搜索和头发撕裂,我什至不确定这是否有意义。我该怎么做?

更新
根据评论;这里有一些可以复制/粘贴的代码,带有测试用例。
如您所见,它在测试 [4] 和 [10] 中失败。

/*
Test data (tree):
0
    1
        2
        3
            4
        5
    6
        7
            8
            9
    10
        11
            12
                13
                14

Test cases:
f([1])      returns [2,4,5]
f([1,2])    returns [2]
f([1,3])    returns [4]
f([1,2,3])  returns [2,4]
f([3])      returns [4]
f([4])      returns [4]
f([1,6])    returns [2,4,5,8,9]
f([11])     returns [13, 14]
f([10])     returns [13, 14]

*/

$test[] = array('in'=>[1],      'out' => [2,4,5]);
$test[] = array('in'=>[1,2],    'out' => [2]);
$test[] = array('in'=>[1,3],    'out' => [4]);
$test[] = array('in'=>[1,2,3],  'out' => [2,4]);
$test[] = array('in'=>[1,2,5],  'out' => [2,5]);
$test[] = array('in'=>[3],      'out' => [4]);
$test[] = array('in'=>[4],      'out' => [4]);
$test[] = array('in'=>[1,6],    'out' => [2,4,5,8,9]);
$test[] = array('in'=>[11],     'out' => [13, 14]);
$test[] = array('in'=>[10],     'out' => [13, 14]);

echo '<pre>';
foreach($test as $t) {
    echo 'input: ' . implode(',',$t['in']) .' '.PHP_EOL;
    $r = f($t['in']);
    echo 'output: ' . implode(',',$r) .' ';
    if($r == $t['out']) {
        echo '(ok)';
    }
    else {
        echo '(TEST FAIL)'.PHP_EOL;
        echo 'got: ' . implode(',',$r) .' '.PHP_EOL;
        echo 'expected: ' . implode(',',$t['out']) .' ';
    }
    echo PHP_EOL.PHP_EOL;
}




function f($ids) {
    $nodes = getEndNodes(getTree(), $ids);
    return array_map(function($a){return $a['id'];},$nodes);    
}

function getEndNodes($tree, $ids, $force=false) {
    $end_nodes = array();
    foreach($tree as $el) {

        if(!empty($el['children'])) {

            // if given ids is in some of these children, search only those
            $children = array();
            foreach($el['children'] as $child) {
                if(in_array($child['element']['id'], $ids)) { $children[] = $child; }
            }
            // if no children in ids, search all (normal search)
            if(!empty($children)) {
                $end_nodes = array_merge($end_nodes, getEndNodes($children, $ids));
            }
            // if this element is in the given ids, force search
            elseif(in_array($el['element']['id'], $ids)) {
                $end_nodes = array_merge($end_nodes, getEndNodes($el['children'], $ids, true));
            }
            // if this is a force search
            elseif($force) {
                $end_nodes = array_merge($end_nodes, getEndNodes($el['children'], $ids));
            }
            else {
                // ??
            }
        }
        else {
            $end_nodes[] = $el['element'];
        }

    }
    return $end_nodes;
}


function getList() {
    $list = array(
        array('id'=>1,'parent_id'=>0),
        array('id'=>2,'parent_id'=>1),
        array('id'=>3,'parent_id'=>1),
        array('id'=>4,'parent_id'=>3),
        array('id'=>5,'parent_id'=>1),
        array('id'=>6,'parent_id'=>0),
        array('id'=>7,'parent_id'=>6),
        array('id'=>8,'parent_id'=>7),
        array('id'=>9,'parent_id'=>7),
        array('id'=>10,'parent_id'=>0),
        array('id'=>11,'parent_id'=>10),
        array('id'=>12,'parent_id'=>11),
        array('id'=>13,'parent_id'=>12),
        array('id'=>14,'parent_id'=>12),
    );
    return $list;
}

function getTree() {
    $hash = array();
    $list = getList();
    foreach($list as $el) {
        $hash[$el['id']] = array('element' => $el);
    }
    foreach($hash as $id => &$node) {
        if ($parent_id = $node['element']['parent_id']) {
            $hash[$parent_id]['children'][] =& $node;
        }
        else {
            $tree[] =& $node;
        }
    }
    unset($node, $hash);
    return $tree;
}

输出

input: 1 
output: 2,4,5 (ok)

input: 1,2 
output: 2 (ok)

input: 1,3 
output: 4 (ok)

input: 1,2,3 
output: 2,4 (ok)

input: 1,2,5 
output: 2,5 (ok)

input: 3 
output: 4 (ok)

input: 4 
output:  (TEST FAIL)
got:  
expected: 4 

input: 1,6 
output: 2,4,5,8,9 (ok)

input: 11 
output: 13,14 (ok)

input: 10 
output:  (TEST FAIL)
got:  
expected: 13,14 

【问题讨论】:

标签: php tree


【解决方案1】:

又过了一天,我想我明白了,它并不漂亮,但据我所知,它确实有效。如果有人需要这个:

<?php

/*
Test data (tree) (0 is fictional) :
0
    1
        2
        3
            4
        5
    6
        7
            8
            9
    10
        11
            12
                13
                14
    15
    16
        17

*/

$tests = [
    ['in'=>[1],       'out' => [2,4,5]],
    ['in'=>[1,2],     'out' => [2]],
    ['in'=>[1,3],     'out' => [4]],
    ['in'=>[1,2,3],   'out' => [2,4]],
    ['in'=>[1,2,5],   'out' => [2,5]],
    ['in'=>[3],       'out' => [4]],
    ['in'=>[4],       'out' => [4]],
    ['in'=>[1,6],     'out' => [2,4,5,8,9]],
    ['in'=>[11],      'out' => [13,14]],
    ['in'=>[10],      'out' => [13,14]],
    ['in'=>[1,6,13],  'out' => [2,4,5,8,9,13]],
    ['in'=>[8,12,6],  'out' => [8,13,14]],
    ['in'=>[5],       'out' => [5]],
    ['in'=>[0],       'out' => []],
    ['in'=>[15],      'out' => [15]],
    ['in'=>[16],      'out' => [17]],
    ['in'=>[15,16],   'out' => [15,17]],
    ['in'=>[999],     'out' => []],
];

foreach($tests as $t) {
    echo 'input: ' . implode(',',$t['in']) .' '.PHP_EOL;
    $r = f($t['in']);
    echo 'output: ' . implode(',',$r) .' ';
    if($r == $t['out']) {
        echo '(ok)';
    }
    else {
        echo '(TEST FAIL)'.PHP_EOL;
        echo 'got: ' . implode(',',$r) .' '.PHP_EOL;
        echo 'expected: ' . implode(',',$t['out']) .' ';
    }
    echo PHP_EOL.PHP_EOL;
}

function f($ids) {
    $nodes = getEndNodes($ids, getTree());
    return array_map(function($a){return $a['id'];},$nodes);
}

function getEndNodes(array $ids, $tree=false, $force=false, $skip=false) {

    if(!$ids)   throw new Exception("No ids given");
    if(!$tree)  throw new Exception("No tree given");

    $end_nodes = array();
    foreach($tree as $el) {

        if(!empty($el['children'])) {

            // If given ids is in some of these children, search only those to skip their siblings.
            $children = array();
            foreach($el['children'] as $child) {
                if(in_array($child['element']['id'], $ids)) { $children[] = $child; }
            }
            if(!empty($children)) {
                $end_nodes = array_merge($end_nodes, getEndNodes($ids, $children));
            }
            // If the current element is in the input ids, force search down this tree. This will get end nodes in deep branches.
            elseif(in_array($el['element']['id'], $ids) || $force === true) {
                $end_nodes = array_merge($end_nodes, getEndNodes($ids, $el['children'], true));
            }
            // If nothing else; continue normal search, but only add input ids, not parents. The force will find them.
            else {
                $end_nodes = array_merge($end_nodes, getEndNodes($ids, $el['children'], false, true));
            }
        }
        else {
            if(!$skip && ($el['element']['parent_id'] != 0)) {
                $end_nodes[] = $el['element'];
            }
            elseif (in_array($el['element']['id'], $ids) && $el['element']['parent_id'] == 0) {
                $end_nodes[] = $el['element'];
            }
        }
    }
    return $end_nodes;
}


function getList() {
    return [
        ['id'=>1,'parent_id'=>0],
        ['id'=>2,'parent_id'=>1],
        ['id'=>3,'parent_id'=>1],
        ['id'=>4,'parent_id'=>3],
        ['id'=>5,'parent_id'=>1],
        ['id'=>6,'parent_id'=>0],
        ['id'=>7,'parent_id'=>6],
        ['id'=>8,'parent_id'=>7],
        ['id'=>9,'parent_id'=>7],
        ['id'=>10,'parent_id'=>0],
        ['id'=>11,'parent_id'=>10],
        ['id'=>12,'parent_id'=>11],
        ['id'=>13,'parent_id'=>12],
        ['id'=>14,'parent_id'=>12],
        ['id'=>15,'parent_id'=>0],
        ['id'=>16,'parent_id'=>0],
        ['id'=>17,'parent_id'=>16],
    ];
}

function getTree() {
    $hash = array();
    $list = getList();
    foreach($list as $el) {
        $hash[$el['id']] = array('element' => $el);
    }
    foreach($hash as $id => &$node) {
        if ($parent_id = $node['element']['parent_id']) {
            $hash[$parent_id]['children'][] =& $node;
        }
        else {
            $tree[] =& $node;
        }
    }
    unset($node, $hash);
    return $tree;
}

输出

input: 1
output: 2,4,5 (ok)

input: 1,2
output: 2 (ok)

input: 1,3
output: 4 (ok)

input: 1,2,3
output: 2,4 (ok)

input: 1,2,5
output: 2,5 (ok)

input: 3
output: 4 (ok)

input: 4
output: 4 (ok)

input: 1,6
output: 2,4,5,8,9 (ok)

input: 11
output: 13,14 (ok)

input: 10
output: 13,14 (ok)

input: 1,6,13
output: 2,4,5,8,9,13 (ok)

input: 8,12,6
output: 8,13,14 (ok)

input: 5
output: 5 (ok)

input: 0
output:  (ok)

input: 15
output: 15 (ok)

input: 16
output: 17 (ok)

input: 15,16
output: 15,17 (ok)

input: 999
output:  (ok)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-13
    • 1970-01-01
    • 1970-01-01
    • 2013-05-18
    • 2021-06-27
    • 1970-01-01
    • 1970-01-01
    • 2021-10-11
    相关资源
    最近更新 更多