我有以下数据:
var data = [
{ index : 1, sort : 10, parent : 0 },
{ index : 2, sort : 7, parent : 0 },
{ index : 3, sort : 15, parent : 1 },
{ index : 4, sort : 4, parent : 0 },
{ index : 5, sort : 13, parent : 1 },
{ index : 6, sort : 20, parent : 5 },
{ index : 7, sort : 2, parent : 8 },
{ index : 8, sort : 6, parent : 5 },
];
如何通过父ID和排序值对此进行有效排序,以便最终得到:
var data = [
{ index : 4, sort : 4, parent : 0 },
{ index : 2, sort : 7, parent : 0 },
{ index : 1, sort : 10, parent : 0 },
{ index : 5, sort : 13, parent : 1 },
{ index : 8, sort : 6, parent : 5 },
{ index : 7, sort : 2, parent : 8 },
{ index : 6, sort : 20, parent : 5 },
{ index : 3, sort : 15, parent : 1 },
];
这是树结构。每个元素后面紧跟着任何子元素,同一分支上的所有元素都按排序值排序。
我能想到的最好的方法是先按父母排序,然后对每个分支进行第二次排序。这似乎效率低下。
编辑:示例排序顺序错误。我已经纠正了它。
编辑以澄清:每个嵌套分支都需要显示在父值的正下方,而不是在分支的末尾。
编辑:对数据的进一步更正。
答案 0 :(得分:16)
这不是您的原始方法,但您可以根据数据构建实际树,如下所示:
function TreeNode(data) {
this.data = data;
this.parent = null;
this.children = [];
}
TreeNode.comparer = function (a, b) {
return a.data.sort < b.data.sort ? 0 : 1;
};
TreeNode.prototype.sortRecursive = function () {
this.children.sort(TreeNode.comparer);
for (var i=0, l=this.children.length; i<l; i++) {
this.children[i].sortRecursive();
}
return this;
};
function toTree(data) {
var nodeById = {}, i = 0, l = data.length, node;
nodeById[0] = new TreeNode(); // that's the root node
for (i=0; i<l; i++) { // make TreeNode objects for each item
nodeById[ data[i].index ] = new TreeNode(data[i]);
}
for (i=0; i<l; i++) { // link all TreeNode objects
node = nodeById[ data[i].index ];
node.parent = nodeById[node.data.parent];
node.parent.children.push(node);
}
return nodeById[0].sortRecursive();
}
通过这种设置,您可以通过简单的调用整齐地嵌套节点:
var tree = toTree(data);
TreeNode:0 parent -> null data -> undefined childen -> Array[ TreeNode:1 parent -> TreeNode:0 data -> { index : 4, sort : 4, parent : 0 } childen -> Array[] TreeNode:2 parent -> TreeNode:0 data -> { index : 2, sort : 7, parent : 0 } childen -> Array[] TreeNode:3 parent -> TreeNode:0 data -> { index : 1, sort : 10, parent : 0 } childen -> Array[ TreeNode:4 parent -> TreeNode:3 data -> { index : 5, sort : 13, parent : 1 } childen -> Array[ ] TreeNode:5 parent -> TreeNode:3 data -> { index : 3, sort : 15, parent : 1 } childen -> Array[ ... and so on ... ] ] ]
一旦你拥有了这个树对象,就可以用它做很多事情,包括以预期的顺序递归遍历它。
为此,您可以添加一个帮助函数,该函数执行深度优先遍历并为每个节点执行有效负载函数f
:
TreeNode.prototype.walk = function(f, recursive) {
for (var i=0, l=this.children.length; i<l; i++) {
var child = this.children[i];
f.apply(child, Array.prototype.slice.call(arguments, 2));
if (recursive) child.walk.apply(child, arguments);
}
}
并将其称为:
tree.walk(function () { console.log(this.data) }, true);
会产生:
{ index: 4, sort: 4, parent: 0} { index: 2, sort: 7, parent: 0} { index: 1, sort: 10, parent: 0} { index: 5, sort: 13, parent: 1} { index: 8, sort: 6, parent: 5} { index: 7, sort: 2, parent: 8} { index: 6, sort: 20, parent: 5} { index: 3, sort: 15, parent: 1}
将更复杂的有效负载函数用于其他效果,例如将带有jQuery的表中的表行或项添加到<select>
框中。
答案 1 :(得分:2)
Tomalak请求上面我发布我的单身版本的答案。这是:
/**
* Represents sorted results in a tree structure.
*/
Tree = (function() {
/**
*
* @type {Object} nodes Holds all the nodes in a flat format.
* @type {Object} nodes.data The data that is held in this node.
* @type {Object} nodes.parent Points to the parent object of this node.
* @type {Array} nodes.children An array of the child nodes of this node.
*/
var nodes = {};
/**
* @type {Object} root_node A Reference to the root node in nodes.
*/
var root_node;
/**
* A sort function to sort the nodes by the data.sort value in each node.
* @param {Number} a The first node to compare
* @param {Number} b The second node to compare
* @return {Boolean} Swap these nodes or not.
*/
var comparer = function (a, b) {
return a.data.sort < b.data.sort ? 0 : 1;
};
/**
* Sorts all the nodes so that they are in the correct order according to each nodes data.sort value.
* @param {Object} node A reference to the node in the nodes object.
*/
var sortRecursive = function (node) {
node.children.sort(comparer);
var len = node.children.length;
for (var i = 0 ; i < len ; i++) {
sortRecursive(node.children[i]);
}
};
/**
* Create a new node with the passed in data.
* @param {Object} data The data that is associated with this node.
*/
var create_node = function(data){
var node = {
data : data,
parent : null,
children : []
};
return node;
};
return {
/**
* Create a new tree of data
* @param {Array} data An array of data objects to transorm into a tree.
* @param {Array} data[].index The id of this node
* @param {Array} data[].parent The parent id of this node.
* @param {Number} root_id Id of the root node.
*/
create : function(data, root_id){
// Clear any previous data
nodes = {};
var i;
var len = data.length;
// Create an empty root node
nodes[root_id] = create_node({});
root_node = nodes[root_id];
// Make node objects for each data item
for (i=0; i<len; i++) {
if(typeof data[i].sort !== "undefined")
nodes[ data[i].index ] = create_node(data[i]);
}
// Link all TreeNode objects
for (i=0; i<len; i++) {
var node = nodes[data[i].index];
node.parent = nodes[node.data.parent];
node.parent.children.push(node);
}
sortRecursive(nodes[root_id]);
},
/**
* Walk through the nodes in nested and then sorted order, calling the passed in callback for each node.
* @param {Function} callback A callback function to call for each node.
* @param {Boolean} recursive Should the walkback be recursive, or just fetch the top level results.
* @param {Object|Undefined} node The node that is currently being walked.
* Ommit this value and the root node will be used.
*/
walk : function(callback, recursive, node) {
if(typeof node == "undefined")
node = root_node;
for (var i = 0, len = node.children.length; i < len; i++) {
var child = node.children[i];
callback.apply(child, Array.prototype.slice.call(arguments, 2));
if (recursive)
arguments.callee(callback, recursive, child);
}
}
};
})();
用以下代码填充树:
Tree.create(unsorted_data, parent_id);
使用以下内容获取已排序的数组:
var sorted = [];
Tree.walk(function(){
sorted.push(this.data);
}, true);
答案 2 :(得分:0)
编辑:scar这个,它不起作用。
这是我管理过的最好的。哪个应该冒泡它。我还没有测试过它。我会留下最好的答案,看看是否有人可以改进它。
data.sort(function(a, b) {
return a.parent - b.parent;
});
var changes = true;
while (changes){
changes = false;
for (var i = 1; i < data.length; i++) {
if(data[i].parent === data[i-1].parent && data[i].sort < data[i-1].sort){
var tmp = data[i-1];
data[i-1] = data[i];
data[i] = tmp;
changes = true;
}
}
}
答案 3 :(得分:0)
经过多次尝试,我想出了这个。它有效,但不是很优雅。也可以抽象到自己的班级。
// Initial sort places items in the correct sort order.
data.sort(function(a, b) {
return a.sort - b.sort;
});
vat top_parent_id = 1; // The value of an items parent if it is a top level item.
var sorted = []; // Empty array to copy sorted elements into.
var skipped = true; // flag to indicate if any elements have been skipped.
var skip_count = 0; // Counter to prevent infinite loops.
// Loop until no elements have been skipped.
//This loops through each level of the tree until all nested branches have been added
while(skipped){
skipped = false;
skip_count++;
if(skip_count === 50){ // Maximum tree depth.
throw "Error in sorted data or tree depth too great.";
break;
}
// Loop through the data in reverse order; this preserves the branch sort order.
for (var i = data.length - 1; i >= 0; i--) {
var item = data[i];
// Skip if this element has already been processed.
if(item.done)
continue;
// If this is this a top level item, then insert and continue.
if(item.parent == top_parent_id){
sorted.splice(0, 0, item); // Insert at the start; last item is the top sort.
item.done = true;
continue;
}
// Loop the new array to try and find this items parent.
var found = false;
for (var j = 0; j < sorted.length; j++) {
if(item.parent === sorted[j].index){
sorted.splice(j + 1, 0, item);
found = true;
item.done = true;
break;
}
}
// If a place for an item has not been found then skip it for now so it can be tested again on the next iteration.
if(!found){
skipped = true;
}
}
}
data = sorted;