Building hierarchy objects from flat list of parent/child

前端 未结 6 510
花落未央
花落未央 2021-01-30 18:07

I have a list of items in a hierarchy, and I\'m attempting to parse this list out into an actual hierarchy of objects. I\'m using modified pre-order tree traversal to store/iter

相关标签:
6条回答
  • 2021-01-30 18:35

    Alternative version that compiles normally, I wasn't sure if there was a problem with the code above.

    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;
        }
    
    0 讨论(0)
  • 2021-01-30 18:37

    you should use a database store architecture like nested set

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

    0 讨论(0)
  • 2021-01-30 18:38

    Here is an example, hope this helps

    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; } }
    }
    

    }

    0 讨论(0)
  • 2021-01-30 18:39

    Here's the function I ended up writing. I'm using MPTT to store objects, so the list is in order of the 'left' value, which basically means the parent always comes before any given item in the list. In other words, the item referenced by item.ParentID has always already been added (except in the case of top-level or root nodes).

    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;
    }
    

    and a simple test (that works in 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();
    }
    

    Results:

    enter image description here

    Since I'm updating this 5 years later, here's a recursive LINQ version:

    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();
    }
    
    0 讨论(0)
  • 2021-01-30 18:48

    I assume you already know the parent of all items.

    All you need to do is to iterate through all item of the list once and add the current item to its parent's children list. Only keep the items without parents in the target nested list.

    Here is some pseudo code:

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

    As for getting the parents, from what I can see in your example, you might need to parse the name and build a stack as you advance in the list.

    This problems looks hard but it really isn't. A lot of people see this problem from the wrong angle; you must not try to populate every children list but rather get rid of the children items from the flat list, then it becomes easy.

    0 讨论(0)
  • 2021-01-30 18:58

    Correcting the example given by 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();
        }
    
    0 讨论(0)
提交回复
热议问题