从父/子的平面列表构建层次结构对象

Posted

技术标签:

【中文标题】从父/子的平面列表构建层次结构对象【英文标题】:Building hierarchy objects from flat list of parent/child 【发布时间】:2010-09-24 01:30:59 【问题描述】:

我有一个层次结构中的项目列表,我正在尝试将这个列表解析为实际的对象层次结构。我正在使用modified pre-order tree traversal 来存储/遍历此列表,因此我拥有的是树的一个子集,包括所有子节点,按其“左”值排序。

例如,给定树:

项目 A 项目 A.1 项目 A.2 项目 A.2.2 项目 B B.1 项 C 项

我得到了清单:

项目 A、项目 A.1、项目 A.2、项目 A.2.2、项目 B、项目 B.1、项目 C

(这是按照修改后的预购树设置中“左”值的顺序)。

我想要做的是将其解析为包含树的实际结构的对象,例如:

Class TreeObject 
    String Name;
    Guid ID; 
    Guid ParentID;
    List<TreeObject> Children;

平面列表作为 TreeObject 列表返回 - 每个 TreeObject 都有 ID、ParentID、Left 和 Right 属性。我正在寻找的是一个函数:

List<TreeObject> FlatToHeirarchy(List<TreeObject> list); 

它接受平面列表,并返回一个嵌套列表。

换句话说:

List<TreeObject> flatSet = LoadTreeObjectsFromDatabase(); 
// flatSet.count == 7; flatSet(0).Children == null
List<TreeObject> nestedSet = FlatToHeirarchy(flatSet);
// nestedSet.count == 3; nestedSet(0).Children.count == 2

我不知道如何做到这一点 - 跟踪父母,并能够处理更大的跳跃(例如,项目 A.2.2 -> 项目 B)。


编辑:我在这里寻找非暴力解决方案(例如,不循环多次,将项目移动到子节点,直到只剩下***父节点)。我猜有一种优雅的方法可以循环一次,然后根据需要放置项目。

请记住,它们总是按层次顺序排列(因为我使用的是 MPTT),因此给定的项目将始终是前一个项目的子项或兄弟项,或者至少与前一个项目共享一个父项。它永远不会出现在树的其他地方。

【问题讨论】:

这是否意味着您存储了db中每个节点的parentId和左右值? 【参考方案1】:

这是我最终编写的函数。我正在使用 MPTT 来存储对象,因此列表按“左”值的顺序排列,这基本上意味着父级始终位于列表中的任何给定项目之前。换句话说,item.ParentID 所引用的项目始终已经被添加(除了***或根节点的情况)。

public class TreeObject

    public int Id  get; set; 
    public int ParentId  get; set; 
    public string Name  get; set; 
    public IList<TreeObject> Children  get; set;  = new List<TreeObject>();


public IEnumerable<TreeObject> FlatToHierarchy(List<TreeObject> list)

    // hashtable lookup that allows us to grab references to containers based on id
    var lookup = new Dictionary<int, TreeObject>();
    // actual nested collection to return
    var nested = new List<TreeObject>();

    foreach (TreeObject item in list)
    
        if (lookup.ContainsKey(item.ParentId))
        
            // add to the parent's child list 
            lookup[item.ParentId].Children.Add(item);
        
        else
        
            // no parent added yet (or this is the first time)
            nested.Add(item);
        
        lookup.Add(item.Id, item);
    

    return nested;

和一个简单的测试(在 LinqPad 中有效):

void Main()

    var list = new List<TreeObject>() 
        new TreeObject()  Id = 1, ParentId = 0, Name = "A" ,
        new TreeObject()  Id = 2, ParentId = 1, Name = "A.1" ,
        new TreeObject()  Id = 3, ParentId = 1, Name = "A.2" ,
        new TreeObject()  Id = 4, ParentId = 3, Name = "A.2.i" ,
        new TreeObject()  Id = 5, ParentId = 3, Name = "A.2.ii" 
    ;

    FlatToHierarchy(list).Dump();

结果:

因为我在 5 年后更新这个,这里是一个递归 LINQ 版本:

public IList<TreeObject> FlatToHierarchy(IEnumerable<TreeObject> list, int parentId = 0) 
    return (from i in list 
            where i.ParentId == parentId 
            select new TreeObject 
                Id = i.Id, 
                ParentId = i.ParentId,
                Name = i.Name,
                Children = FlatToHierarchy(list, i.Id)
            ).ToList();

【讨论】:

这行是怎么回事:lookup(item.ParentID).Item(item.ParentID).Add(item); ?这是一个错字吗?您只能在该级别按索引访问项目,不是吗? ScottE:不,lookup 是字典,由 Guid 索引。这有点令人困惑,但这是因为 lookup(item.PranteID) 包含 item.parentId 的 PARENT 列表,或者换句话说,该项目的祖父母)。所以 .item(item.parentId) 包含这个项目所在的列表,并且单个项目被添加为兄弟。 我正在尝试实现这一点,但我发现查找是一个变量,但正在像方法一样使用,是否缺少某些东西,我认为没有,我只是做错了什么? 这是 .net 吗?它编译吗?我在下面添加了您的代码的返工,可以立即编译,我做错了吗? 有人能修复这个例子吗?【参考方案2】:

我假设你已经知道所有项目的父项

您需要做的就是遍历列表的所有项目一次并将当前项目添加到其父项的子列表中。仅将没有父项的项保留在目标嵌套列表中。

这是一些伪代码:

foreach Item item in flatlist
   if item.Parent != null
      Add item to item.Parent.ChildrenList
      Remove item from flatlist
   end if
end for

至于获取父母,从我在您的示例中可以看到,您可能需要在列表中前进时解析名称并构建堆栈。

这个问题看起来很难,但实际上并非如此。很多人从错误的角度看待这个问题;您不能尝试填充每个子列表,而是从平面列表中删除子项,这样就变得容易了。

【讨论】:

那是我没有得到的部分。我猜有一种很好的方法来拥有一个包含所有父项的堆栈,并在项目比其父项更深时继续推入它,或者在你上升时弹出最后一个。我要避免的是在列表中循环多次删除项目。 一旦你知道每个节点的父节点,你就可以遍历列表一次。我用一些伪代码更新了我的答案。 我知道父对象的 ID,但我没有对父对象本身的引用(当然没有搜索列表)。 你的“平面列表”是什么样的,这将极大地帮助你理解你的答案......我不能只是猜测你推断它是什么或包含什么。 在某些语言中,例如 C#,这将不起作用,因为编译器会在运行时抛出 hissy-fit,因为您修改了它正在枚举的集合。【参考方案3】:

你应该使用像嵌套集这样的数据库存储架构

[https://en.wikipedia.org/wiki/Nested_set_model]

【讨论】:

【参考方案4】:

替代版本可以正常编译,不知道上面的代码有没有问题。

private List<Page> FlatToHierarchy(List<Page> list) 
      // hashtable lookup that allows us to grab references to the parent containers, based on id
        Dictionary<int, Page> lookup = new Dictionary<int, Page>();
      // actual nested collection to return
      List<Page> nested = new List<Page>();

      foreach(Page item in list) 
        if (lookup.ContainsKey(item.parentId)) 
          // add to the parent's child list 
          lookup[item.parentId].children.Add(item); //add item to parent's childs list
          lookup.Add(item.pageId, item); //add reference to page in lookup table
         else 
          // no parent added yet (or this is the first time)
          nested.Add(item);     //add item directly to nested list                
          lookup.Add(item.pageId, item); //add reference to page in lookup table
        
      
    return nested;
    

【讨论】:

【参考方案5】:

举个例子,希望对你有帮助

class Program

    static void Main(string[] args)
    
        TreeObject a  = new TreeObject()  Name = "Item A" ;
        a.Children.Add( new TreeObject()  Name = "Item A.1" );
        a.Children.Add( new TreeObject()  Name = "Item A.2" );

        TreeObject b = new TreeObject()  Name = "Item B" ;
        b.Children.Add(new TreeObject()  Name = "Item B.1" );
        b.Children.Add(new TreeObject()  Name = "Item B.2" );

        TreeObject c = new TreeObject()  Name = "Item C" ;

        List<TreeObject> nodes = new List<TreeObject>(new[]  a, b, c );

        string list = BuildList(nodes);
        Console.WriteLine(list); // Item A,Item A.1,Item A.2,Item B,Item B.1,Item B.2,Item C

        List<TreeObject> newlist = new List<TreeObject>();
        TreeObject temp = null;

        foreach (string s in list.Split(','))
        
            if (temp == null || !s.Contains(temp.Name) || temp.Name.Length != s.Length)
            
                temp = new TreeObject()  Name = s ;
                newlist.Add(temp);
            
            else
            
                temp.Children.Add(new TreeObject()  Name = s );
                                          
        

        Console.WriteLine(BuildList(newlist)); // Item A,Item A.1,Item A.2,Item B,Item B.1,Item B.2,Item C
    

    static string BuildList(List<TreeObject> nodes)
    
        StringBuilder output = new StringBuilder();
        BuildList(output, nodes);
        return output.Remove(output.Length - 1, 1).ToString();
    

    static void BuildList(StringBuilder output, List<TreeObject> nodes)
    
        foreach (var node in nodes)
        
            output.AppendFormat("0,", node.Name);
            BuildList(output, node.Children);
        
    


public class TreeObject

    private List<TreeObject> _children = new List<TreeObject>();

    public string Name  get; set; 
    public Guid Id  get; set; 
    public List<TreeObject> Children  get  return _children;  

【讨论】:

这几乎与我想做的相反。我想要结束的是您在第 15 行显示的“节点”变量中的结构。我从一个 List 开始,其中直接包含所有对象,作为兄弟姐妹(例如,不存在父/子关系) .【参考方案6】:

更正gregmac

给出的例子
IList<TreeObject> FlatToHierarchy(IQueryable<lcc_classe> list, int? parentId)
    
        var q = (from i in list
                where i.parent_id == parentId
                select new
                
                    id = i.id,
                    parent_id = i.parent_id,
                    kks = i.kks,
                    nome = i.nome
                ).ToList();
        return q.Select(x => new TreeObject
            
                children = FlatToHierarchy(list, x.id)
            ).ToList();
    

【讨论】:

您对此进行了测试吗,我喜欢您的方法...并且想自己在平板上尝试一下。

以上是关于从父/子的平面列表构建层次结构对象的主要内容,如果未能解决你的问题,请参考以下文章

使用 json 对象列表以角度生成层次结构视图

从现有的层次结构创建不可变的集合结构

根据内部元素的 ID 将平面 xml 转换为树层次结构

ats 分层缓存

使用Cosmos Client返回Azure Cosmos项目的平面层次结构

用于搜索资源的 RESTFul 平面层次结构与动态层次结构