【问题标题】:Count elements in adjacent array计算相邻数组中的元素
【发布时间】:2011-06-18 16:16:33
【问题描述】:

我有一个具有层次结构的数组。在这个例子中,第一层有两个元素,83 和 82。 82 有子元素 84 和 87 。 87是84的孩子。

Array
(
[_children] => Array
    (
        [0] => Array
            (
                [id] => 11
                [uid] => 24
                [rid] => 83
                [date] => 2011-06-18 15:08:10
            )

        [1] => Array
            (
                [id] => 10
                [uid] => 24
                [rid] => 82
                [date] => 2011-06-18 15:08:07
                [_children] => Array
                    (
                        [0] => Array
                            (
                                [id] => 12
                                [uid] => 82
                                [rid] => 84
                                [date] => 2011-06-18 15:30:34
                                [_children] => Array
                                    (
                                        [0] => Array
                                            (
                                                [id] => 13
                                                [uid] => 84
                                                [rid] => 87
                                                [date] => 2011-06-18 16:11:50
                                            )

                                    )

                            )

                    )

            )

    )

)

我想遍历该数组并将每个级别的一些信息存储在另一个数组中。这就是我的数组最后的样子。

 Array
    (  
      [0] => Array
      (
           [elements] => 2
      )

     [1] => Array
      (
           [elements] => 1
      )

    )

例如这表明在级别 0 有 3 个节点,在级别 1 有 5 个节点。我怎样才能做到这一点?有什么想法吗?

【问题讨论】:

  • 不是0级有2个节点,1级有1个节点吗...?
  • 最后一个数组只是结果的示例,但我对其进行了编辑
  • 这个数组是否定义了深度?

标签: php arrays


【解决方案1】:

我只会数数。

这意味着基本键的每个项目加上它的孩子的相同。

与递归相反,我决定为此使用堆栈

要在每个级别都有一个值,计数器还需要一个级别上下文,它被添加到节点旁边的堆栈中:

$count = array();
$stack[] = array(-1, $arr);
while($item = array_shift($stack)) {
    list($level, $node) = $item;
    $count[$level] = isset($count[$level]) ? $count[$level]+1 : 1;
    if (!isset($node['_children'])) 
        continue;
    foreach($node['_children'] as $item)
        $stack[] = array($level+1, $item);
}
var_dump($count);

这确实输出:

array(4) {
  [-1]=> int(1)
  [0] => int(2)
  [1] => int(1)
  [2] => int(1)
}

其中-1 是仅包含子节点的根节点。因此,您可能希望在处理后将其删除:

array_shift($count);

编辑:存储节点

以下代码还为每个级别添加了一个节点收集器,称为$nodes。根据count($nodes[$level])$count[$level] 有点多余,但这只是为了展示如何收集节点:

$count = array();
$nodes = array(); // nodes per level go here.
$stack[] = array(-1, $arr);
while($item = array_shift($stack)) {
    list($level, $node) = $item;
    $count[$level] = isset($count[$level]) ? $count[$level]+1 : 1;
    $nodes[$level][] = $node; // set here!
    if (!isset($node['_children'])) 
        continue;
    foreach($node['_children'] as $item)
        $stack[] = array($level+1, $item);
}
var_dump($count, $nodes);

讨论

使用堆栈可防止进行递归函数调用并降低复杂性。其背后的想法相当简单:

  1. 使用第一个节点(仅包含子节点)初始化堆栈 - 树的根节点。
  2. 堆栈上的条目包含要计数的节点及其级别
  3. 堆栈中的每个元素都被同等对待:
    1. 一项从堆栈的开头(或结尾)移除。
    2. 它的等级计数器加一。
    3. 如果它有子节点,则每个子节点都将添加到堆栈及其级别。
  4. 将处理堆栈,直到消耗完所有元素。

这可确保以最小的开销计算每个节点。可以完全控制消耗堆栈的策略。 FIFO (First In, First Out)、LIFO (Last In, First Out) - 易于实现 - 甚至是加权策略(首先处理没有/最多子节点的子节点以快速消耗它们)甚至随机堆栈消耗将数据结构所暗示的约束分布在整个堆栈中。

比较栈和递归函数栈

首先将使用自己的堆栈与 PHP 的函数堆栈进行比较表明,这两种方式都有共同点。两者都使用堆栈。

但主要区别在于,自己的堆栈会阻止使用函数调用。调用函数,尤其是递归调用,有多种可以避免的含义。

另一个关键区别是,自己的堆栈始终允许对其进行询问,而 PHP 语言自己的堆栈无法控制。

除此之外,可以顺序处理自己的堆栈,而函数堆栈是递归构造,将数据处理 1:1 映射到程序流中。

虽然递归将隐藏函数代码的数据(树)的递归性质(这可以简化处理),但需要提供被隐藏的状态函数调用作为函数参数引入。

因此,管理数据以解决问题并不比递归更复杂,而是相同或更复杂。

因此,除了函数调用的开销之外,人们已经可以闻到数据开销。

从 PHP 语言本身的角度来看这是值得的。

为了使递归工作,PHP 需要在函数堆栈中添加额外的变量表。这些表需要在每次函数调用前后进行管理。

递归还需要你通过引用传递变量。这是额外的开销,因为这些值需要在函数结束之前和之后由 PHP 设置/处理。

递归中需要这样的引用来共享计数器的数据结构。递归实现中的这种开销可以通过在 PHP 中将递归实现到它自己的类中来减少,目前还没有建议,可能值得考虑。

就像用户堆栈实现中的计数器数组一样,这样的类可以在所有函数调用之间共享计数器,从而为类的成员函数提供可访问的散列,而无需使用变量引用。此外,解决问题的复杂性可以分布在类中的不同函数上,甚至可以作为依赖项注入。 PHP 已经提供了默认的recursive iterator interface

另一个可以减少使用的地方是全局变量表 ($GLOBALS),但缺点是代码的可重用性降低,这表明存在设计缺陷。即使设计不是问题,也需要在函数调用之间管理所谓的超全局数组。这导致了与通过引用传递非常相似的行为,它被认为可以阻止它。因此,这不是一个可取的解决方案。

类递归实现

这是基于上面讨论中提出的想法的递归实现,以便更好地掌握它:

/**
 * class implementation of a recursive child counter
 * 
 * Usage:
 * 
 *   Object use:
 * 
 *    $counter = new LevelChildCounter($arr, '_children');
 *    $count = $counter->countPerLevel();
 * 
 *  Functional use:
 * 
 *    $count = LevelChildCounter::count($arr, '_children');
 */
class LevelChildCounter {
    private $tree;
    private $childKey;
    private $counter;
    public function __construct(array $tree, $childKey) {
        $this->tree = $tree;
        $this->childKey = $childKey;
    }
    /**
     * functional interface of countPerLevel
     */
    public static function count(array $tree, $childKey) {
        $counter = new self($tree, $childKey);
        return $counter->countPerLevel();
    }
    private function countUp($level) {
        isset($this->counter[$level])
          ? $this->counter[$level]++
          : $this->counter[$level] = 1;
    }
    private function countNode($node, $level) {
        // count node
        $this->countUp($level);

        // children to handle?        
        if (!isset($node[$this->childKey]))
            return;

        // recursively count child nodes            
        foreach($node[$this->childKey] as $childNode)
                $this->countNode($childNode, $level+1)
                ;
    }
    /**
     * Count all nodes per level
     * 
     * @return array
     */
    public function countPerLevel() {
        $this->counter = array();
        $this->countNode($this->tree, -1);
        return $this->counter;
    }
}

$count = LevelChildCounter::count($arr, '_children');
array_shift($count);

var_dump($count);

它的输出:

array(3) {
  [0]=> int(2)
  [1]=> int(1)
  [2]=> int(1)
}

【讨论】:

  • 最后一件事。是否可以存储一个数组而不是节点的数量?这个数组应该包含节点的数量和每个级别节点的所有 UID 的列表。你能用这个plz扩展你的第一个代码吗?我被困在这个...
  • ArtWorkAD:不计算,只添加子节点。我进行了编辑并为此添加了第二个示例。
  • 很棒的帖子。请注意,递归和迭代之间存在持续的战斗,因为这是一个品味问题。在我看来,递归解决方案更好更简洁,因为它可以减少代码。然而,在 PHP 中,不是一种函数式编程语言,对于大多数程序员来说,迭代解决方案可能更容易理解。
  • 是的,我在讨论部分有过。我认为作为一名程序员,您应该了解这两种方式。这有点短,因为我没有在这里做真正的指标,而且课程也运行得很好。所以一如既往地取决于。对于遍历目录,我绝对可以推荐一个堆栈而不是递归。如果您有兴趣,我在类似的问题中遇到了另一种有趣的递归形式:stackoverflow.com/questions/6416812/…
【解决方案2】:

似乎递归函数是实现这一点的最简单方法。

该函数应以_children 数组作为参数,并应保留结果、“结果”数组和级别计数器。

函数应该像getStructure($myarray['_children'])那样调用,它调用基函数_getStructure($array, &$results, $depth)$results最初是一个空数组,$depth设置为零。

函数 _getStructure 然后循环遍历数组 (foreach) 并增加 $results[$depth]++。如果子项包含_children 字段,则此函数再次调用_getStructure,并增加$detph++

_getStructure 返回后,$results 数组被您给定的结构填充。这是由于引用传递,它将$results 数组传递给函数而不复制它:对它的所有更改都可以在调用它的范围内看到。见PHP docs

【讨论】:

    【解决方案3】:

    我假设你有一个固定的深度,将它调整为无限深度会很简单:

    <?php 
    
    function count_child( $node, &$results, $depth, $curr_depth = 0){
        if( $curr_depth >= $depth) 
            return;
        if( is_set( $results[$curr_depth] ))
            $results[$curr_depth] += count( $node['_children'] );
        else
            $results[$curr_depth] = 1;
        if( is_array( $node ) && is_set( $node['_children']) )
            foreach( $node['_children'] as $next_node){
                count_child( $next_node, $result , $depth , $curr_depth + 1);
            } 
    
    }
    ?>
    

    【讨论】:

    • 如果您没有固定深度,您可以简单地删除第一个 if 语句广告,然后在 for 循环之后添加一个 return
    • @ArtoAle:在递增之前不要忘记初始化$results[$curr_depth]
    • 另外,$curr_depth 应该是一个可选参数。
    • @ArtoAle 谢谢,但我得到一个为 foreach 错误提供的无效参数
    • sry,您应该先检查 $node 变量是否是一个数组,然后您可以对其进行迭代,我将编辑我的答案并进行一些更正
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-22
    相关资源
    最近更新 更多