如何从平面结构有效地建造树木?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何从平面结构有效地建造树木?相关的知识,希望对你有一定的参考价值。
我有一堆扁平结构的物体。这些物体有ID
和ParentID
属性,因此它们可以安排在树上。它们没有特别的顺序。每个ParentID
属性不一定与结构中的ID
匹配。因此它们可能是从这些物体中出现的几棵树。
您将如何处理这些对象以创建生成的树?
我不是一个解决方案,但我确信它远非最佳...
我需要创建这些树,然后按正确的顺序将数据插入数据库。
没有循环引用。当ParentID == null或在其他对象中找不到ParentID时,Node是RootNode
将对象的ID存储在映射到特定对象的哈希表中。枚举所有对象并找到它们的父项(如果存在)并相应地更新其父指针。
class MyObject
{ // The actual object
public int ParentID { get; set; }
public int ID { get; set; }
}
class Node
{
public List<Node> Children = new List<Node>();
public Node Parent { get; set; }
public MyObject AssociatedObject { get; set; }
}
IEnumerable<Node> BuildTreeAndGetRoots(List<MyObject> actualObjects)
{
Dictionary<int, Node> lookup = new Dictionary<int, Node>();
actualObjects.ForEach(x => lookup.Add(x.ID, new Node { AssociatedObject = x }));
foreach (var item in lookup.Values) {
Node proposedParent;
if (lookup.TryGetValue(item.AssociatedObject.ParentID, out proposedParent)) {
item.Parent = proposedParent;
proposedParent.Children.Add(item);
}
}
return lookup.Values.Where(x => x.Parent == null);
}
这是Mehrdad Afshari的答案的java解决方案。
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class Tree {
Iterator<Node> buildTreeAndGetRoots(List<MyObject> actualObjects) {
Map<Integer, Node> lookup = new HashMap<>();
actualObjects.forEach(x -> lookup.put(x.id, new Node(x)));
//foreach (var item in lookup.Values)
lookup.values().forEach(item ->
{
Node proposedParent;
if (lookup.containsKey(item.associatedObject.parentId)) {
proposedParent = lookup.get(item.associatedObject.parentId);
item.parent = proposedParent;
proposedParent.children.add(item);
}
}
);
//return lookup.values.Where(x =>x.Parent ==null);
return lookup.values().stream().filter(x -> x.parent == null).iterator();
}
}
class MyObject { // The actual object
public int parentId;
public int id;
}
class Node {
public List<Node> children = new ArrayList<Node>();
public Node parent;
public MyObject associatedObject;
public Node(MyObject associatedObject) {
this.associatedObject = associatedObject;
}
}
你是否只使用这些属性?如果没有,那么创建子节点数组可能会很好,您可以在其中循环遍历所有这些对象以构建此类属性。从那里,选择具有子节点但没有父节点的节点,并从上到下迭代地构建您的树。
我可以在4行代码和O(n log n)时间内完成此操作,假设Dictionary类似于TreeMap。
dict := Dictionary new.
ary do: [:each | dict at: each id put: each].
ary do: [:each | (dict at: each parent) addChild: each].
root := dict at: nil.
编辑:好的,现在我读到一些parentID是假的,所以忘记上面的内容,并执行以下操作:
dict := Dictionary new.
dict at: nil put: OrderedCollection new.
ary do: [:each | dict at: each id put: each].
ary do: [:each |
(dict at: each parent ifAbsent: [dict at: nil])
add: each].
roots := dict at: nil.
大多数答案都假设您希望在数据库之外执行此操作。如果您的树本质上是相对静态的,并且您只需要以某种方式将树映射到数据库中,您可能需要考虑在数据库端使用嵌套集表示。查看Joe Celko的书籍(或者here获得Celko的概述)。 如果仍然绑定到Oracle dbs,请查看他们的CONNECT BY以获得直接的SQL方法。 无论使用哪种方法,您都可以在将数据加载到数据库之前完全跳过映射树。只是想我会提供这个替代方案,它可能完全不适合您的特定需求。原始问题的整个“正确顺序”部分在某种程度上意味着您需要在数据库中由于某种原因使命令“正确”?这可能会促使我在那里处理树木。
这与提问者所寻求的并不完全相同,但我很难绕过这里提供的含糊不清的答案,我仍然认为这个答案符合标题。
我的答案是将平面结构映射到直接在对象树上,你所拥有的只是每个对象上的ParentID
。 ParentID
是null
或0
,如果它是根。在提问者的对面,我假设所有有效的ParentID
指向列表中的其他内容:
var rootNodes = new List<DTIntranetMenuItem>();
var dictIntranetMenuItems = new Dictionary<long, DTIntranetMenuItem>();
//Convert the flat database items to the DTO's,
//that has a list of children instead of a ParentID.
foreach (var efIntranetMenuItem in flatIntranetMenuItems) //List<tblIntranetMenuItem>
{
//Automapper (nuget)
DTIntranetMenuItem intranetMenuItem =
Mapper.Map<DTIntranetMenuItem>(efIntranetMenuItem);
intranetMenuItem.Children = new List<DTIntranetMenuItem>();
dictIntranetMenuItems.Add(efIntranetMenuItem.ID, intranetMenuItem);
}
foreach (var efIntranetMenuItem in flatIntranetMenuItems)
{
//Getting the equivalent object of the converted ones
DTIntranetMenuItem intranetMenuItem = dictIntranetMenuItems[efIntranetMenuItem.ID];
if (efIntranetMenuItem.ParentID == null || efIntranetMenuItem.ParentID <= 0)
{
rootNodes.Add(intranetMenuItem);
}
else
{
var parent = dictIntranetMenuItems[efIntranetMenuItem.ParentID.Value];
parent.Children.Add(intranetMenuItem);
//intranetMenuItem.Parent = parent;
}
}
return rootNodes;
这是一个ruby实现:
它将按属性名称或方法调用的结果进行编目。
CatalogGenerator = ->(depth) do
if depth != 0
->(hash, key) do
hash[key] = Hash.new(&CatalogGenerator[depth - 1])
end
else
->(hash, key) do
hash[key] = []
end
end
end
def catalog(collection, root_name: :root, by:)
method_names = [*by]
log = Hash.new(&CatalogGenerator[method_names.length])
tree = collection.each_with_object(log) do |item, catalog|
path = method_names.map { |method_name| item.public_send(method_name)}.unshift(root_name.to_sym)
catalog.dig(*path) << item
end
tree.with_indifferent_access
end
students = [#<Student:0x007f891d0b4818 id: 33999, status: "on_hold", tenant_id: 95>,
#<Student:0x007f891d0b4570 id: 7635, status: "on_hold", tenant_id: 6>,
#<Student:0x007f891d0b42c8 id: 37220, status: "on_hold", tenant_id: 6>,
#<Student:0x007f891d0b4020 id: 3444, status: "ready_for_match", tenant_id: 15>,
#<Student:0x007f8931d5ab58 id: 25166, status: "in_partnership", tenant_id: 10>]
catalog students, by: [:tenant_id, :status]
# this would out put the following
{"root"=>
{95=>
{"on_hold"=>
[#<Student:0x007f891d0b4818
id: 33999,
status: "on_hold",
tenant_id: 95>]},
6=>
{"on_hold"=>
[#<Student:0x007f891d0b4570 id: 7635, status: "on_hold", tenant_id: 6>,
#<Student:0x007f891d0b42c8
id: 37220,
status: "on_hold",
tenant_id: 6>]},
15=>
{"ready_for_match"=>
[#<Student:0x007f891d0b4020
id: 3444,
status: "ready_for_match",
tenant_id: 15>]},
10=>
{"in_partnership"=>
[#<Student:0x007f8931d5ab58
id: 25166,
status: "in_partnership",
tenant_id: 10>]}}}
接受的答案对我来说太复杂了,所以我要添加它的Ruby和NodeJS版本
假设平面节点列表具有以下结构:
nodes = [
{ id: 7, parent_id: 1 },
...
] # ruby
nodes = [
{ id: 7, parentId: 1 },
...
] # nodeJS
将上面的平面列表结构转换为树的功能看起来如下
对于Ruby:
def to_tree(nodes)
nodes.each do |node|
parent = nodes.find { |another| another[:id] == node[:parent_id] }
next unless parent
node[:parent] = parent
parent[:children] ||= []
parent[:children] << node
end
nodes.select { |node| node[:parent].nil? }
end
对于NodeJS:
const toTree = (nodes) => {
nodes.forEach((node) => {
const parent = nodes.find((another) => another.id == node.parentId)
if(parent == null) return;
node.parent = parent;
parent.children = parent.children || [];
parent.children = parent.children.concat(node);
});
return nodes.filter((node) => node.pare以上是关于如何从平面结构有效地建造树木?的主要内容,如果未能解决你的问题,请参考以下文章
如何有效地检索树中节点的路径(与帖子“将平面表解析为树?”相关)
如何有效地链接来自 Kafka Streams 中平面 api 数据的 groupby 查询?