按 parent 排序(拓扑排序)和数组元素的重要性
Sort by parent (topological sort) and importance of element on array
好吧,我有一个 objects 的数组,其中一些元素依赖于其他元素。
因此,我需要按重要性(parent 的依赖性)对其进行排序,以将其存储在数据库中并替换所有 children 的 parent
属性由各自 parent id
.
数组示例:
[
{
"id": 1,
"email": "a@b.com", // unique
"parent": "c@b.com" // is nullable
},
{
"id": 2,
"email": "b@b.com",
"parent": null
},
{
"id": 3,
"email": "c@b.com",
"parent": "b@b.com"
},
{
"id": 4,
"email": "d@b.com",
"parent": "a@b.com"
},
...
]
依赖关系的图形示例:
预期结果:
按依赖项排序 (parent):
[
{
"id": 2,
"email": "b@b.com",
"parent": null
},
{
"id": 3,
"email": "c@b.com",
"parent": 2
},
{
"id": 1,
"email": "a@b.com",
"parent": 3
},
{
"id": 4,
"email": "d@b.com",
"parent": 1
},
...
]
设置各自的 parent id
我正在使用(但它没有按 parent 级别排序:parent、children, 盛大children...):
let users = [
{
"id": 1,
"email": "a@b.com", // unique
"parent": "c@b.com" // is nullable
},
{
"id": 2,
"email": "b@b.com",
"parent": null
},
{
"id": 3,
"email": "c@b.com",
"parent": "b@b.com"
},
{
"id": 4,
"email": "d@b.com",
"parent": "a@b.com"
}
];
users = users.map(user => {
user.parent = _.findIndex(users, i => user.parent === i.email);
return user;
});
P.S:
在这种情况下,importance
概念指的是 parent
级别。
所以,首先我需要 parents,然后是 children、grandchildren,等等...
很抱歉,如果这个帖子解释得不好,如果您有疑问,我会寻找表达想法的最佳方式。
你可以使用递归函数
const data = [{
"id": 1,
"email": "a@b.com", // unique
"parent": "c@b.com" // is nullable
},
{
"id": 2,
"email": "b@b.com",
"parent": null
},
{
"id": 3,
"email": "c@b.com",
"parent": "b@b.com"
},
{
"id": 4,
"email": "d@b.com",
"parent": "a@b.com"
},
]
const order = (arr, level) => {
const children = arr.filter(e => e.parent === level); // get the elements that have the same parent ( level )
const parent = arr.find(e => e.email === level); // get the parent
return children.length
? [
...children.map(e =>
({ ...e,
parent: parent ? parent.id : null // update the parent to the id instead of email
})),
...order(arr, children[0].email) // call the same function with the email of the first child of the current children array, it will become a parent
]
: children // otherwise return the array
}
const result = order(data, null)
console.log(result)
我将首先生成一个新输入,用 parent id
替换 parent email
,并生成与它们所属的树相关的节点级别的新 属性。然后我们可以按 level
属性 对节点进行排序,在相等的 level
上,我们按 id
.
排序
const input = [
{"id": 1, "email": "a@b.com", "parent": "c@b.com"},
{"id": 2, "email": "b@b.com", "parent": null},
{"id": 3, "email": "c@b.com", "parent": "b@b.com"},
{"id": 4, "email": "d@b.com", "parent": "a@b.com"},
{"id": 5, "email": "x@b.com", "parent": "b@b.com"},
{"id": 6, "email": "z@b.com", "parent": "x@b.com"},
{"id": 7, "email": "y@b.com", "parent": null},
{"id": 8, "email": "m@b.com", "parent": "y@b.com"}
];
const findParent = (mail) => input.find(x => x.email === mail);
const getLevel = (mail, lvl) =>
{
return mail ? getLevel(findParent(mail).parent, lvl + 1) : lvl;
}
let newInput = input.map(({id, email, parent}) =>
{
return {
id: id,
email: email,
parent: findParent(parent) ? findParent(parent).id : null,
lvl: getLevel(parent, 0)
};
});
let sortedInput = newInput.sort((a, b) =>
{
return (a.lvl - b.lvl) ? a.lvl - b.lvl : a.id - b.id;
});
console.log(sortedInput);
下面是一种迭代方法(与提供的递归解决方案相对),您可以使用它来获得结果。基本上,首先找到根元素,然后遍历原始数组,查找以当前元素为父元素的元素。
要实现用 ID 替换父电子邮件,只需保留父名称到 ID 的映射:
var data = [{
"id": 1,
"email": "a@b.com", // unique
"parent": "c@b.com" // is nullable
}, {
"id": 2,
"email": "b@b.com",
"parent": null
}, {
"id": 3,
"email": "c@b.com",
"parent": "b@b.com"
}, {
"id": 4,
"email": "d@b.com",
"parent": "a@b.com"
}]
//Map email addresses to IDs
var map = data.reduce((accum, el) => {
accum[el.email] = {
id: el.id
}
return accum;
}, {});
var [root] = data.filter(el => !el.parent);
var users = [root];
var cur;
var children;
while (users.length < data.length) {
cur = users[users.length - 1];
//Find elments that have cur as parent
children = data.filter(el => el.parent === cur.email);
children.forEach(el => {
users.push({
id: el.id,
email: el.email,
parent: map[el.parent].id
});
});
}
console.log(users)
所有提供的答案都很好,但时间复杂度较慢 (O(n^2))。他们遍历所有节点,并为每个节点搜索其父节点(遍历节点 - O(n),并在每次迭代中寻找父节点 - O(n) * O(n) = O( n^2))
更好的解决方案是创建树结构并使用预排序 (DFS) 创建拓扑排序。
function createTree(nodesWithParentArray) {
const initialTree = nodesWithParentArray.reduce(
(acc, node) => {
acc[node.id] = { data: node, children: [] }
return acc
},
{ null: { children: [] } }
)
const tree = nodesWithParentArray.reduce((acc, node) => {
acc[node.parent].children.push(acc[node.id])
return acc
}, initialTree)
return tree[null].children[0] // root
}
// test it like that:
createTree([{id:1, parent:2},{id:2, parent:null},{id:3, parent:2},{id:4, parent:3}])
上面的函数 return 一个嵌套的树结构,带有指向根节点的指针。剩下要做的是使用预序遍历来创建拓扑排序(O(n),因为我们只遍历每个节点一次):
function topologicalSort(tree) {
const sortedList = []
const queue = [treeRoot]
while (queue.length) {
const curNode = queue.shift()
sortedList.push(curNode.data)
queue.push(...curNode.children)
}
return sortedList
}
创建上面的树也是 O(n) 因为它只在输入数组上循环一次,所以最终的时间复杂度是 O(n) + O(n) => O(n)
好吧,我有一个 objects 的数组,其中一些元素依赖于其他元素。
因此,我需要按重要性(parent 的依赖性)对其进行排序,以将其存储在数据库中并替换所有 children 的 parent
属性由各自 parent id
.
数组示例:
[
{
"id": 1,
"email": "a@b.com", // unique
"parent": "c@b.com" // is nullable
},
{
"id": 2,
"email": "b@b.com",
"parent": null
},
{
"id": 3,
"email": "c@b.com",
"parent": "b@b.com"
},
{
"id": 4,
"email": "d@b.com",
"parent": "a@b.com"
},
...
]
依赖关系的图形示例:
预期结果: 按依赖项排序 (parent):
[
{
"id": 2,
"email": "b@b.com",
"parent": null
},
{
"id": 3,
"email": "c@b.com",
"parent": 2
},
{
"id": 1,
"email": "a@b.com",
"parent": 3
},
{
"id": 4,
"email": "d@b.com",
"parent": 1
},
...
]
设置各自的 parent id
我正在使用(但它没有按 parent 级别排序:parent、children, 盛大children...):
let users = [
{
"id": 1,
"email": "a@b.com", // unique
"parent": "c@b.com" // is nullable
},
{
"id": 2,
"email": "b@b.com",
"parent": null
},
{
"id": 3,
"email": "c@b.com",
"parent": "b@b.com"
},
{
"id": 4,
"email": "d@b.com",
"parent": "a@b.com"
}
];
users = users.map(user => {
user.parent = _.findIndex(users, i => user.parent === i.email);
return user;
});
P.S:
在这种情况下,importance
概念指的是 parent
级别。
所以,首先我需要 parents,然后是 children、grandchildren,等等...
很抱歉,如果这个帖子解释得不好,如果您有疑问,我会寻找表达想法的最佳方式。
你可以使用递归函数
const data = [{
"id": 1,
"email": "a@b.com", // unique
"parent": "c@b.com" // is nullable
},
{
"id": 2,
"email": "b@b.com",
"parent": null
},
{
"id": 3,
"email": "c@b.com",
"parent": "b@b.com"
},
{
"id": 4,
"email": "d@b.com",
"parent": "a@b.com"
},
]
const order = (arr, level) => {
const children = arr.filter(e => e.parent === level); // get the elements that have the same parent ( level )
const parent = arr.find(e => e.email === level); // get the parent
return children.length
? [
...children.map(e =>
({ ...e,
parent: parent ? parent.id : null // update the parent to the id instead of email
})),
...order(arr, children[0].email) // call the same function with the email of the first child of the current children array, it will become a parent
]
: children // otherwise return the array
}
const result = order(data, null)
console.log(result)
我将首先生成一个新输入,用 parent id
替换 parent email
,并生成与它们所属的树相关的节点级别的新 属性。然后我们可以按 level
属性 对节点进行排序,在相等的 level
上,我们按 id
.
const input = [
{"id": 1, "email": "a@b.com", "parent": "c@b.com"},
{"id": 2, "email": "b@b.com", "parent": null},
{"id": 3, "email": "c@b.com", "parent": "b@b.com"},
{"id": 4, "email": "d@b.com", "parent": "a@b.com"},
{"id": 5, "email": "x@b.com", "parent": "b@b.com"},
{"id": 6, "email": "z@b.com", "parent": "x@b.com"},
{"id": 7, "email": "y@b.com", "parent": null},
{"id": 8, "email": "m@b.com", "parent": "y@b.com"}
];
const findParent = (mail) => input.find(x => x.email === mail);
const getLevel = (mail, lvl) =>
{
return mail ? getLevel(findParent(mail).parent, lvl + 1) : lvl;
}
let newInput = input.map(({id, email, parent}) =>
{
return {
id: id,
email: email,
parent: findParent(parent) ? findParent(parent).id : null,
lvl: getLevel(parent, 0)
};
});
let sortedInput = newInput.sort((a, b) =>
{
return (a.lvl - b.lvl) ? a.lvl - b.lvl : a.id - b.id;
});
console.log(sortedInput);
下面是一种迭代方法(与提供的递归解决方案相对),您可以使用它来获得结果。基本上,首先找到根元素,然后遍历原始数组,查找以当前元素为父元素的元素。
要实现用 ID 替换父电子邮件,只需保留父名称到 ID 的映射:
var data = [{
"id": 1,
"email": "a@b.com", // unique
"parent": "c@b.com" // is nullable
}, {
"id": 2,
"email": "b@b.com",
"parent": null
}, {
"id": 3,
"email": "c@b.com",
"parent": "b@b.com"
}, {
"id": 4,
"email": "d@b.com",
"parent": "a@b.com"
}]
//Map email addresses to IDs
var map = data.reduce((accum, el) => {
accum[el.email] = {
id: el.id
}
return accum;
}, {});
var [root] = data.filter(el => !el.parent);
var users = [root];
var cur;
var children;
while (users.length < data.length) {
cur = users[users.length - 1];
//Find elments that have cur as parent
children = data.filter(el => el.parent === cur.email);
children.forEach(el => {
users.push({
id: el.id,
email: el.email,
parent: map[el.parent].id
});
});
}
console.log(users)
所有提供的答案都很好,但时间复杂度较慢 (O(n^2))。他们遍历所有节点,并为每个节点搜索其父节点(遍历节点 - O(n),并在每次迭代中寻找父节点 - O(n) * O(n) = O( n^2))
更好的解决方案是创建树结构并使用预排序 (DFS) 创建拓扑排序。
function createTree(nodesWithParentArray) {
const initialTree = nodesWithParentArray.reduce(
(acc, node) => {
acc[node.id] = { data: node, children: [] }
return acc
},
{ null: { children: [] } }
)
const tree = nodesWithParentArray.reduce((acc, node) => {
acc[node.parent].children.push(acc[node.id])
return acc
}, initialTree)
return tree[null].children[0] // root
}
// test it like that:
createTree([{id:1, parent:2},{id:2, parent:null},{id:3, parent:2},{id:4, parent:3}])
上面的函数 return 一个嵌套的树结构,带有指向根节点的指针。剩下要做的是使用预序遍历来创建拓扑排序(O(n),因为我们只遍历每个节点一次):
function topologicalSort(tree) {
const sortedList = []
const queue = [treeRoot]
while (queue.length) {
const curNode = queue.shift()
sortedList.push(curNode.data)
queue.push(...curNode.children)
}
return sortedList
}
创建上面的树也是 O(n) 因为它只在输入数组上循环一次,所以最终的时间复杂度是 O(n) + O(n) => O(n)