Build tree type list by recursively checking parent-child relationship C#

前端 未结 3 803
暗喜
暗喜 2020-12-04 10:49

I have One class that has a list of itself so it can be represented in a tree structure.

I am pulling a flat list of these classes and want to unflatten it.

相关标签:
3条回答
  • 2020-12-04 11:10

    I have no idea why you want your BuildTree method return List<Group> - tree needs to have root node, so you should expect it to return single Group element, not a list.

    I would create an extension method on IEnumerable<Group>:

    public static class GroupEnumerable
    {
        public static IList<Group> BuildTree(this IEnumerable<Group> source)
        {
            var groups = source.GroupBy(i => i.ParentID);
    
            var roots = groups.FirstOrDefault(g => g.Key.HasValue == false).ToList();
    
            if (roots.Count > 0)
            {
                var dict = groups.Where(g => g.Key.HasValue).ToDictionary(g => g.Key.Value, g => g.ToList());
                for (int i = 0; i < roots.Count; i++)
                    AddChildren(roots[i], dict);
            }
    
            return roots;
        }
    
        private static void AddChildren(Group node, IDictionary<int, List<Group>> source)
        {
            if (source.ContainsKey(node.ID))
            {
                node.Children = source[node.ID];
                for (int i = 0; i < node.Children.Count; i++)
                    AddChildren(node.Children[i], source);
            }
            else
            {
                node.Children = new List<Group>();
            }
        }
    }
    

    Usage

    var flatList = new List<Group>() {
        new Group() { ID = 1, ParentID = null },    // root node
        new Group() { ID = 2, ParentID = 1 },
        new Group() { ID = 3, ParentID = 1 },
        new Group() { ID = 4, ParentID = 3 },
        new Group() { ID = 5, ParentID = 4 },
        new Group() { ID = 6, ParentID = 4 }
    };
    
    
    var tree = flatList.BuildTree();
    
    0 讨论(0)
  • 2020-12-04 11:13

    I tried solutions suggested and figured out that they give us about O(n^2) complexity.

    In my case (I have about 50k items to be built into tree) it was completely unacceptable.

    I came to the following solution (assuming that each item has only one parent and all parents exist in the list) with complexity O(n*log(n)) [n times getById, getById has O(log(n)) complexity]:

    static List<Item> BuildTreeAndReturnRootNodes(List<Item> flatItems)
    {
        var byIdLookup = flatItems.ToLookup(i => i.Id);
        foreach (var item in flatItems)
        {
            if (item.ParentId != null)
            {
                var parent = byIdLookup[item.ParentId.Value].First();
                parent.Children.Add(item);
            }
        }
        return flatItems.Where(i => i.ParentId == null).ToList();
    }
    

    Full code snippet:

    class Program
    {
        static void Main(string[] args)
        {
            var flatItems = new List<Item>()
            {
                new Item(1),
                new Item(2),
                new Item(3, 1),
                new Item(4, 2),
                new Item(5, 4),
                new Item(6, 3),
                new Item(7, 5),
                new Item(8, 2),
                new Item(9, 3),
                new Item(10, 9),
            };
            var treeNodes = BuildTreeAndReturnRootNodes(flatItems);
            foreach (var n in treeNodes)
            {
                Console.WriteLine(n.Id + " number of children: " + n.Children.Count);
            }
        }
        // Here is the method
        static List<Item> BuildTreeAndReturnRootNodes(List<Item> flatItems)
        {
            var byIdLookup = flatItems.ToLookup(i => i.Id);
            foreach (var item in flatItems)
            {
                if (item.ParentId != null)
                {
                    var parent = byIdLookup[item.ParentId.Value].First();
                    parent.Children.Add(item);
                }
            }
            return flatItems.Where(i => i.ParentId == null).ToList();
        }
        class Item
        {
            public readonly int Id;
            public readonly int? ParentId;
    
            public Item(int id, int? parent = null)
            {
                Id = id;
                ParentId = parent;
            }
            public readonly List<Item> Children = new List<Item>();
        }
    }
    
    0 讨论(0)
  • 2020-12-04 11:15

    Here's how you can do this in one line:

    static void BuildTree(List<Group> items)
    {
        items.ForEach(i => i.Children = items.Where(ch => ch.ParentID == i.ID).ToList());
    }
    

    You can just call it like this:

    BuildTree(flatList);
    

    If at the end you want to get the nodes whose parent is null (i.e. the top-level nodes), you can simply do this:

    static List<Group> BuildTree(List<Group> items)
    {
        items.ForEach(i => i.Children = items.Where(ch => ch.ParentID == i.ID).ToList());
        return items.Where(i => i.ParentID == null).ToList();
    }
    

    And if you want to make it an extension method, you can just add this in the method signature:

    static List<Group> BuildTree(this List<Group> items)
    

    Then you can call it like this:

    var roots = flatList.BuildTree();
    
    0 讨论(0)
提交回复
热议问题