【问题标题】:Remove childless elements (except leafs) from nested array从嵌套数组中删除无子元素(叶子除外)
【发布时间】:2019-07-30 07:39:54
【问题描述】:

我有一个名为mappings 的表,其中包含idleafparent_idnameflip_parent 列。 parent_idflip_parent 都是引用 mapping.id 列的整数。 flip_parent 拥有一个应该从树中排除的值的 id。为此,我有以下函数($mappingsmappings 表中的所有行,flipParentIds 是同一表中的所有值,其中 flip_parent 值不是 null

private function removeFlipParents(array $mappings, array $flipParentIds)
{
    foreach ($mappings as $key => $mapping) {
        foreach ($flipParentIds as $id) {
            if ($mapping['id'] === $id['flipParent']) {
                unset($mappings[$key]);
            }
        }
    }

    return $mappings;
}

删除这些值后,我需要用剩余的数据构建一棵树(树的深度为 5/6 层),这是通过以下代码完成的;

private function buildTree(array $elements, $parentId)
{
    $branch = [];

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

    return $branch;
}

在这种情况下,elements$mappings 是相同的数组,但没有那些翻转的父母。此函数的结果作为 JSON 响应返回并由 Javascript 处理以构建树。返回的 JSON 有类似这样的结构;

[{
    "id": 1, "name": "Node 1", "children": [{
      "id": 2, "name": "Node 1.1", "children": [{
        "id": 4, "name": "Node 1.1.1", "leaf": true, "children": [], "gls": [{
          "id": 1000, "name": "GL1", "code": "0100"
        }, {
          "id": 1001, "name": "GL2", "code": "0200"
        }]
      }, {
        "id": 5, "name": "Node 1.1.2", "leaf": true, "children": [], "gls": [{
          "id": 2000, "name": "GL3", "code": "0300"
        }, {
          "id": 2001, "name": "GL4", "code": "0400"
        }]
      }]
    }, {
      "id": 3, "name": "Node 1.2", "children": [{
        "id": 6, "name": "Node 1.2.1", "leaf": true, "children": [], "gls": [{
          "id": 3000, "name": "GL5", "code": "0500"
        }, {
          "id": 3001, "name": "GL6", "code": "0600"
        }]
      }]
    }]
  },
  {
    "id": 7, "name": "Node 2", "children": [{
      "id": 8, "name": "Node 2.1", "children": [{
        "id": 9, "name": "Node 2.1.1", "leaf": true, "children": [], "gls": [{
          "id": 4000, "name": "GL7", "code": "0700"
        }, {
          "id": 4001, "name": "GL8", "code": "0800"
        }]
      }]
    }]
  }
]

要在 JS 中构建树,我有以下两个函数

parseNodes(nodes)
{
    const ul = document.createElement('ul');
    ul.style.listStyleType = 'none';
    nodes.forEach((node) => {
        const parsedNode = this.parseNode(node);
        if (parsedNode) {
            ul.appendChild(parsedNode);
        }
    });
    return ul;
}

parseNode(node)
{
    if (node.name && node.name !== '') {
        const li = document.createElement('li');
        li.className = node.leaf ? 'leaf' : 'parent';
        li.setAttribute('data-mapping-id', node.id);

        if (node.parentId) {
            li.setAttribute('data-parent-id', node.parentId);
        }

        li.innerHTML = node.name;

        if (node.children) {
            li.appendChild(this.parseNodes(node.children));
        }

        return li;
    }
    return null;
}

这一切都很好,但我现在想从树中删除没有子节点的非叶子节点,因为只有叶子实际上是可用的。不知道无子节点在什么级别,也不是所有无子节点都在同一级别。

为此,我编写了一个递归 PHP 函数,称为 after removeFlipParentsbuildTree(这需要完成,因为我需要树结构来检查分支是否有不是的孩子);

private function removeChildlessBranches(array $nodes)
{
    foreach ($nodes as $key => $node) {
        if (empty($node['children']) && !$node['leaf']) {
            unset($nodes[$key]);
        } else {
            $this->removeChildlessBranches($node['children']);
        }
    }

    return $nodes;
}

这有点像我想要它做的,因为它删除了所有不是叶子的无子根节点,但它不会进入第二级及更高级别。它还改变了$nodes 的结构。如果没有 removeChildlessBranches 调用,JSON 将被包装在一个数组 ([{"id": 1, "name": "Node 1", ...}]) 中,我可以在 parseNodes 函数中对其进行迭代,但如果我确实使用 removeChildlessBranches 调用,$nodes 将不再被包装在一个数组 ({"id": 1, "name": "Node 1", ...}) 并且不能被迭代。

所以我的两个问题是,我怎样才能使removeChildlessBranches 真正递归,我怎样才能使它不改变$nodes 的结构?

【问题讨论】:

    标签: javascript php symfony recursion tree


    【解决方案1】:

    您在$this->removeChildlessBranches($node['children']); 这一行传入了$node['children'] 数组的副本(如果您在查找之前没有听说过它,通过引用传递和通过值传递)。因此,任何后续更改都在对该副本进行,而不是您的原始数组(这也是一个副本)。更改的结果随后会被丢弃,因为您没有对它们做任何事情。

    您可以通过将行更改为 $nodes[$key]['children'] = $this->removeChildlessBranches($node['children']); 来解决该问题。

    但是请注意,您现在可能有一个没有子节点且不是叶子的节点,但由于您已经在该级别进行了修剪,因此不会被正确删除。首先修剪孩子然后取消设置应该会给你想要的结果:

    private function removeChildlessBranches(array $nodes)
    {
        foreach ($nodes as $key => $node) {
            $nodes[$key]['children'] = $this->removeChildlessBranches($node['children']);
    
            if (empty($nodes[$key]['children']) && !$nodes[$key]['leaf']) {
                unset($nodes[$key]);
            }
        }
    
        return $nodes;
    }
    

    【讨论】:

    • 谢谢,这似乎按我想要的方式工作!还有一件事,该函数仍然返回一个对象对象,而我需要它是一个对象数组。我该如何执行?
    • PHP 函数将返回一个数组数组。实际上是 json_encode 函数给你带来了麻烦。由于您正在取消设置数组值,因此您现在有“缺失”的数组索引。 JavaScript 不支持乱序数组键,因此 json_encode 的唯一解决方案是将数组编码为对象。一旦我们知道并理解了原因,我们就可以通过使用php.net/manual/en/function.array-values.php 修复数组索引来获得所需的结果,所以我们最终得到json_encode(array_values($nodes));
    • 实际上,这种方法有一个极端情况。 children 可能仍然被编码为对象,因为它们可能仍然有乱序键。为了使事情更清洁并解决该问题,请将removeChildlessBranches 函数的最后一行更改为return array_values($nodes);,这样可以保证您的数组按顺序排列。
    • 这就是我现在所做的,效果很好,谢谢!
    猜你喜欢
    • 2022-07-19
    • 2018-08-29
    • 2021-12-11
    • 2019-01-25
    • 1970-01-01
    • 1970-01-01
    • 2013-11-22
    • 2019-09-13
    相关资源
    最近更新 更多