从自引用数组转换为树中的嵌套数组

时间:2015-03-26 13:56:00

标签: javascript arrays angularjs twitter-bootstrap self-reference

我使用angular-bootstrap-nav-tree

我有来自自引用表的数组像这样:

var obj = [
        {id:1,label:"Animal"},
        {id:2,label:"Vigitable"},
        {id:3,label:"Cats",parent:1},
        {id:4,label:"black cat",parent:3},
        {id:5,label:"orange",parent:2},
    ];

我想将它转换为嵌套如下:

var convrted = [
        {id:1,label:"Animal",children[
         {id:3,label:"Cats",parent:1,children[{id:4,label:"black cat",parent:3}]}
        ]},
        {id:2,label:"Vigitable",children[
         {id:5,label:"orange",parent:2}
        ]}
];

我希望它能够无限制地运作。

2 个答案:

答案 0 :(得分:2)

这将完成这项工作:

function nest (array) {
  nested = [];
  for (var i = 0; i < array.length; i++) {
    var parent = array[i].parent;
    if (!parent) {
      nested.push(array[i]);
    } else {
      // You'll want to replace this with a more efficient search
      for (var j = 0; j < array.length; j++) {
        if (array[j].id === parent) {
          array[j].children = array[j].children || [];
          array[j].children.push(array[i]);
        }
      }
    }
  }
  return nested;
}

第二个for循环是查找父级的低效方式;如果你有多个项目要嵌套,你会想要用不扫描整个数组的东西替换它。

答案 1 :(得分:1)

这是一个称为数据转换的一般操作类的一个实例,它可以非常有趣和有用。我的整体方法是编写递归搜索并删除可在主转换函数中使用的树函数。通常,递归函数不像它们的等效迭代实现那样高效,但它们可以更容易编写和理解。例如,这是我使用的搜索功能:

function getById(tree, id) {
    var len = tree.length;
    for (var i = 0; i < len; i++) {
        if (tree[i].id === id) {
            return tree[i];
        }
    }

    for (var i = 0; i < len; i++) {
        if (tree[i].children) {
            var node = getById(tree[i].children, id);
            if (node) {
                return node;
            }
        }
    }

    return null;
}

这是广度优先搜索的示例,我首先希望在树的顶层找到元素。如果不这样做,我将在每个顶级节点的.children成员上调用搜索功能(如果存在)。如果做不到这一点,我会返回null表示找不到节点。

接下来,我想要一个像Javascript的splice函数一样的递归删除函数。我的想法是从树中提取具有给定ID的节点,以便我可以将其重新插入其父节点的子节点。

function removeFromTree(tree, id) {
    var node = getById(tree, id);
    if (node) {
        if (node.parent) {
            var parent = getById(tree, node.parent);
            if (parent && parent.children) {
                var index = indexInTree(parent.children, id);
                if (index != -1) {
                    return parent.children.splice(index, 1)[0];
                }
            }
        }

        var index = indexInTree(tree, id);
        return tree.splice(index, 1)[0];
    }
    return null;
}

你可以看到我使用splice进行实际的“采摘”,但是splice需要一个索引,所以我写了一个函数来检索给定已知父节点的索引:

function indexInTree(tree, id) {
    var len = tree.length;
    for (var i = 0; i < len; i++) {
        if (tree[i].id === id) {
            return i;
        }
    }

    return -1;
}

这是一个非常简单的操作,其工作方式与数组上的indexof函数非常相似,只是我们只是匹配ID而不是整个对象。

有了这些辅助函数,转换函数非常简单:

function myTransform(array) {
    var tree = array.concat([]);
    var len = array.length;
    for (var i = 0; i < len; i++) {
        if (array[i].parent && (array[i].parent !== array[i].id)) {
            var objToMove = removeFromTree(tree, array[i].id);
            if (objToMove) {
                var parent = getById(tree, objToMove.parent);
                if (parent) {
                    if (!parent.children) {
                        parent.children = [];
                    }
                    parent.children.push(objToMove);
                }
            }
        }
    }
    return tree;
}

首先,我使用concat([])复制原始数组。然后我迭代原始数组。每当我遇到具有父ID的对象时,我都会使用我的删除功能从副本中提取对象。然后,我使用广度优先搜索功能来查找正确的父对象。最后,我将对象插入到父级子列表中。

这可以找到一些简单的测试代码来验证它的工作原理on JSFiddle

Andrew Larson的启发,我决定尝试构建对象而不是数组副本的实现,以便通过id进行O(1)操作搜索。好吧,O(1)假设按键访问对象属性在Javascript运行时内部是O(1)。

function otherTransform(originalArray) {
  var array = {}; // Create the object
  var len = originalArray.length;
  for (var i = 0; i < len; i++) {
    array[originalArray[i].id] = originalArray[i]; // Store by id
  }

  var nested = [];
  for (var i in array) { // Using "in" syntax to iterate over object keys
    var parent = array[i].parent;
    if (!parent) {
      nested.push(array[i]);
    } else {
      array[parent].children = array[parent].children || [];
      array[parent].children.push(array[i]);
    }
  }

  return nested;
}

现在的问题是,您是否真的希望您的数据首先转换为数组样式树。看起来这种新形式的嵌套树在一般情况下会更有用。