2013-04-03 48 views
14

谁能帮亲子以下对象列表转换:转换亲子阵列树

 
[ 
    { 
     "name":"root", 
     "_id":"root_id", 
    }, 
    { 
     "name":"a1", 
     "parentAreaRef":{ 
     "id":"root_id", 
     }, 
     "_id":"a1_id", 
    }, 
    { 
     "name":"a2", 
     "parentAreaRef":{ 
     "id":"a1_id", 
     }, 
     "_id":"a2_id", 
    }, 
    { 
     "name":"a3", 
     "parentAreaRef":{ 
     "id":"a2_id", 
     }, 
     "_id":"a3_id", 
    }, 
    { 
     "name":"b1", 
     "parentAreaRef":{ 
     "id":"root_id", 
     }, 
     "_id":"b1_id", 
    }, 
    { 
     "name":"b2", 
     "parentAreaRef":{ 
     "id":"b1_id", 
     }, 
     "_id":"b2_id", 
    }, 
    { 
     "name":"b3", 
     "parentAreaRef":{ 
     "id":"b1_id", 
     }, 
     "_id":"b3_id", 
    } 
]

成一个树状结构显示父子关系:

 
[ 
    { 
     "name": "root", 
     "_id":"root_id", 
     "children": [ 
      { 
       "name": "a1", 
       "_id":"a1_id", 
       "children" : [ 
        { 
         "name" : "a2", 
         "_id":"a2_id", 
         "children" : [ 
          { 
           "name" : "a3" 
           "_id":"a3_id" 
          } 
         ] 
        } 
       ] 
      }, 
      { 
       "name": "b1", 
       "_id":"b1_id", 
       "children" : [ 
        { 
         "name" : "b2" 
         "_id":"b2_id" 
        }, 
        { 
         "name" : "b3" 
         "_id":"b3_id" 
        } 
       ] 
      } 
     ] 
    } 
] 

(该输出结构是一个允许多个根的数组,但是如果我们可以得到一个解决方案来处理单个根也是很好的。)

输出树看起来像这样:

 

root 
    | 
    -- a1 
    | | 
    | -- a2 
    |  | 
    |  -- a3 
    | 
    -- b1 
     | 
     -- b2 
     -- b3 


谢谢!

+0

很多:)但(显然)没有到一个解决方案呢。我可以发布一些我一直在努力的代码片段,但我认为它们会导致比清晰度更多的混淆 –

+1

@Scobal请发布片段。您可能非常接近解决方案,我们可以告诉您解决方案是什么。 –

+0

您将需要解析和映射。 –

回答

23

我有一个可行的解决方案。我可以给你提示,只要解决它。好处是您的数据不包含任何对节点的前向引用。所以你可以通过数组来创建你的树。如果需要注意,您需要先通过整个阵列来构建一个到节点的ID映射。

你的算法看起来像这样。

  1. 创建一个将id映射到节点的映射。这将使查找节点变得容易。
  2. 循环遍历节点阵列。
  3. 对于每个元素。
    1. 在地图中添加一个条目。
    2. 将一个children属性(一个数组)添加到此节点。
    3. 元素是否有父级?如果不是,则它必须是根,因此将此元素分配给树的根。
    4. 此元素有一个父元素,所以查找父节点,然后将此当前节点添加为父节点的子元素(将其添加到children数组中)。

这应该有助于您解决问题。如果你对这个算法有特殊的问题,我可以指出问题出在哪里,如何解决或发布解决方案,并解释我如何解决它。

UPDATE

我看着你有解决方案。实际上,您不需要为此进行递归,您可以使用上述算法迭代地执行此操作。您也正在修改结构,这使得算法更加复杂。但你有点正确。下面是我如何解决它:

var idToNodeMap = {}; //Keeps track of nodes using id as key, for fast lookup 
var root = null; //Initially set our loop to null 

//loop over data 
data.forEach(function(datum) { 

    //each node will have children, so let's give it a "children" poperty 
    datum.children = []; 

    //add an entry for this node to the map so that any future children can 
    //lookup the parent 
    idToNodeMap[datum._id] = datum; 

    //Does this node have a parent? 
    if(typeof datum.parentAreaRef === "undefined") { 
     //Doesn't look like it, so this node is the root of the tree 
     root = datum;   
    } else {   
     //This node has a parent, so let's look it up using the id 
     parentNode = idToNodeMap[datum.parentAreaRef.id]; 

     //We don't need this property, so let's delete it. 
     delete datum.parentAreaRef; 

     //Let's add the current node as a child of the parent node. 
     parentNode.children.push(datum);   
    } 
}); 

现在root指向整个树。

Fiddle

对于元素数组以任意顺序排列的情况,您必须首先初始化idToNodeMap。该算法的其余部分保持更多或更少相同的(除了在您的节点存储在地图中的路线;这是没有必要的,因为你在第一遍做的话):

var idToNodeMap = data.reduce(function(map, node) { 
    map[node._id] = node; 
    return map; 
}, {}); 
+0

很酷,非常感谢! –

+0

仅供参考,当前小提琴给出:未捕获的类型错误:无法读取属性'#'未定义的错误。否则,我喜欢实现 - 简单而简洁,+1。 –

+0

@ LasseChristiansen-sw_lasse哎呀;我链接到一个旧版本。我修复了这个链接。 –

0

给它一试:

var obj = {}; 
    obj.rootElements = []; 
    var currentRoot; 
    var currentParent; 
    for (s in a) { 
     var t = a[s]; 
     var id = t._id; 
     if (t.parentAreaRef) { 
      var parentId = t.parentAreaRef.id; 
      if (parentId == currentParent._id) { 
       //add children 
       if (!currentParent.children) { 
        currentParent.children = []; 
       } 
       currentParent.children.push(t); 
      } 
      else { 
       addChildToParent(t, parentId); 
      } 

     } 
     else // is root 
     { 
      currentRoot = t; 
      currentParent = t; 
      obj.rootElements.push(currentRoot); 
     } 
    } 

    var t = currentRoot 

    function addChildToParent(child, parentId, root) { 
     for (p in a) { 
      if (a[p]._id.toString() == parentId.toString()) { 
       if (!a[p].children) { 
        a[p].children = []; 
       } 
       a[p].children.push(t); 
      } 
     } 
    } 
2

我知道我太晚了,但因为我刚刚完成我对如何可以做到这一点的样本作的贡献,我想我会分享它,因为它可能会找到有用/或为另一种解决方案提供灵感。

的实施可以在这里找到:http://jsfiddle.net/sw_lasse/9wpHa/

围绕以下递归函数的执行中心的主要思路:

// Get parent of node (recursive) 
var getParent = function (rootNode, rootId) { 

    if (rootNode._id === rootId) 
     return rootNode; 

    for (var i = 0; i < rootNode.children.length; i++) { 
     var child = rootNode.children[i]; 
     if (child._id === rootId) 
      return child; 

     if (child.children.length > 0) 
      var childResult = getParent(child, rootId); 

     if (childResult != null) return childResult; 
    } 
    return null; 
}; 

...用来构建树。

+0

不错,谢谢:) –

2

我知道这是晚了,但我只是完成这个算法,也许它可以帮助一些人寻找解决同样的问题:http://jsfiddle.net/akerbeltz/9dQcn/

关于它的好处是,它并不需要任何特殊的排序在原始对象上。

如果你需要,以使其适应您的需求变化以下行:

  1. 更改_id并根据您的结构parentAreaRef.id。

    if (String(tree[i]._id) === String(item.parentAreaRef.id)) {

  2. 变化取决于你的结构parentAreaRef。

    if (tree[idx].parentAreaRef) buildTree(tree, tree.splice(idx, 1)[0])

希望它能帮助!

+0

这对我很有用。谢谢 – Enkode

0

有一个在你的字符串

a[p].children.push(t); 

错误应该

a[p].children.push(child); 

还我有点优化它:

var data = [{"id":1,"name":"X","parentId":null},{"id":2,"name":"Y","parentId":1},{"id":3,"name":"D","parentId":2},{"id":2,"name":"S","parentId":1},{"id":5,"name":"K","parentId":4}] 
    var obj = {}; 
    obj.rootElements = []; 
    for (i in data) { 
     var _elem = data[i]; 
     if (_elem.parentId) { 
      var _parentId = _elem.parentId; 
      if (_parentId == _elem.id) { 
       // check children, if false - add 
       if (!_elem.children) { 
        _elem.children = []; 
       } 
       _elem.children.push(_elem); 
      } 
      else { 
       addChildToParent(_elem, _parentId); 
      } 
     } 
     else // is root 
     { 
      obj.rootElements.push(_elem); 
     } 
    } 
    function addChildToParent(child, parentId, root) { 
     for (j in data) { 
      if (data[j].id.toString() == parentId.toString()) { 
       if (!data[j].children) { 
        data[j].children = []; 
       } 
       data[j].children.push(child); 
      } 
     } 
    } 
    res.send(obj.rootElements); 
1

您可以使用array-to-tree模块从NPM 。

+1

或者使用我的更高性能的实现(单元测试,100%的代码覆盖率,只有0.5 kb的大小,包括类型):npmjs.com/package/performant-array-to-tree –

0

借助Vivin Paliath的答案缓存逻辑,我创建了一个可重用的函数,将具有子父母关系的数据列表转换为树。

var data = [ 
 
    { "id" : "root"      }, 
 
    { "id" : "a1", "parentId" : "root", }, 
 
    { "id" : "a2", "parentId" : "a1", }, 
 
    { "id" : "a3", "parentId" : "a2", }, 
 
    { "id" : "b1", "parentId" : "root", }, 
 
    { "id" : "b2", "parentId" : "b1", }, 
 
    { "id" : "b3", "parentId" : "b1", } 
 
]; 
 
var options = { 
 
    childKey : 'id', 
 
    parentKey : 'parentId' 
 
}; 
 
var tree = walkTree(listToTree(data, options), pruneChildren); 
 

 
document.body.innerHTML = '<pre>' + JSON.stringify(tree, null, 4) + '</pre>'; 
 

 
function listToTree(list, options) { 
 
    options = options || {}; 
 
    var childKey = options.childKey || 'child'; 
 
    var parentKey = options.parentKey || 'parent'; 
 
    var childrenKey = options.childrenKey || 'children'; 
 
    var nodeFn  = options.nodeFn  || function(node, name, children) { 
 
    return { name : name, children : children }; 
 
    }; 
 
    var nodeCache = {}; 
 
    return list.reduce(function(tree, node) { 
 
    node[childrenKey] = []; 
 
    nodeCache[node[childKey]] = node; 
 
    if (typeof node[parentKey] === 'undefined' || node[parentKey] === '') { 
 
     tree = nodeFn(node, node[childKey], node[childrenKey]); 
 
    } else { 
 
     parentNode = nodeCache[node[parentKey]]; 
 
     parentNode[childrenKey].push(nodeFn(node, node[childKey], node[childrenKey])); 
 
    } 
 
    return tree; 
 
    }, {}); 
 
} 
 

 
function walkTree(tree, visitorFn, parent) { 
 
    if (visitorFn == null || typeof visitorFn !== 'function') { 
 
    return tree; 
 
    } 
 
    visitorFn.call(tree, tree, parent); 
 
    if (tree.children && tree.children.length > 0) { 
 
    tree.children.forEach(function(child) { 
 
     walkTree(child, visitorFn, tree); 
 
    }); 
 
    } 
 
    return tree; 
 
} 
 

 
function pruneChildren(node, parent) { 
 
    if (node.children.length < 1) { 
 
    delete node.children; 
 
    } 
 
}

+0

PHP中的相关问题可以在这里找到:[将一系列父子关系转换为分层树?](http://stackoverflow.com/questions/2915748) –