简体   繁体   中英

Convert a flat list to a multi level hierarchy

I have the following that needs to be converted to a multi level hierarchical tree.

public enum ERootType { NotUsed, RootType1, RootType2, }
public enum ESubType { NotUsed, SubTypeA, SubTypeB, SubTypeC, }

public class Payload { }

public class MyData
{
    public MyData(ERootType rootType, ESubType subType,
         string displayName, Payload payload)
    {
        RootType = rootType;
        SubType = subType;
        DisplayName = displayName;
        Payload = payload;
    }

    public ERootType RootType { get; }
    public ESubType SubType { get; }
    public string DisplayName { get; set; }
    public object Payload { get; }

    public object this[string propertyName]
    {
        get
        {
            Type myType = typeof(MyData);
            PropertyInfo myPropInfo = myType.GetProperty(propertyName);
            return myPropInfo.GetValue(this, null);
        }
        set
        {
            Type myType = typeof(MyData);
            PropertyInfo myPropInfo = myType.GetProperty(propertyName);
            myPropInfo.SetValue(this, value, null);
        }
    }
}

// Create and initialize flat list of data
var Resources = new List<MyData>()
{
    new MyData(ERootType.RootType1, ESubType.SubTypeA, "Item1", new Payload()),
    new MyData(ERootType.RootType1, ESubType.SubTypeA, "Item2", new Payload()),
    new MyData(ERootType.RootType1, ESubType.SubTypeB, "Item3", new Payload()),
    new MyData(ERootType.RootType1, ESubType.SubTypeB, "Item4", new Payload()),
    new MyData(ERootType.RootType1, ESubType.SubTypeC, "Item5", new Payload()),
    new MyData(ERootType.RootType2, ESubType.SubTypeA, "Item6", new Payload()),
    new MyData(ERootType.RootType2, ESubType.SubTypeA, "Item7", new Payload()),
    new MyData(ERootType.RootType2, ESubType.SubTypeB, "Item8", new Payload()),
    new MyData(ERootType.RootType2, ESubType.SubTypeB, "Item9", new Payload()),
    new MyData(ERootType.RootType2, ESubType.SubTypeC, "Item10", new Payload()),
};

I need the above data converted to a hierarchical list that looks something like the following.

  • RootType1 (ERootType = RootType1, ESubType = NotUsed, DiaplayName = "RootType1", Payload = null)
    • SubTypeA (ERootType = NotUsed, ESubType = SubTypeA, DiaplayName = "SubTypeA", Payload = null)
      • Item1 (ERootType = RootType1, ESubType = SubTypeA, DiaplayName = "Item1", Payload = payload object instance)
      • Item2
    • SubTypeB
      • Item3
      • Item4
    • SubTypeC
      • Item5
  • RootType2
    • SubTypeA
      • Item6
      • Item7
    • SubTypeB
      • Item8
      • Item9
    • SubTypeC
      • Item10

I was trying to come up with a solution using Generics and Linq. I created the following class to support the hierarchical results.

public class TreeItem<T>
{
    public T Item { get; set; }
    public IEnumerable<TreeItem<T>> Children { get; set; }
}

I was thinking that it would be good to have a generic method that could generate a tree with n levels by passing in an array of levels which are the names of the properties in the class being converted. I was thinking this method would use recursion. Here's a function declaration I was playing with.

public static IEnumerable<TreeItem<T>> GenerateNLevelTree<T>(this IEnumerable<T> collection,
    string[] levelProps, int currentLevel = 0)

The class MyData contains a contains an indexer to support accessing the each property of MyData using a string.

Any help with a solution for generating the hierarchical data structure would be greatly appreciated.

This might help. If you do something like this:

   var grouped1 = Resources.GroupBy(x => new {x.RootType, x.SubType, x.DisplayName});
   var grouped2 = grouped1.GroupBy(x => new {x.Key.RootType, x.Key.SubType});
   var grouped3 = grouped2.GroupBy(x => new {x.Key.RootType});

and they you traverse through the collections of collections in grouped3 , you get a hierarchy similar to what you describe. You'll probably want to translate that into something more semantically reasonable for your application.

BTW, Thanks for a great repro. All the code needed to try this out was included. That's pretty rare here.

Update

What I did to navigate that grouped3 thing was:

  1. First create a bunch of classes to hold the bits of the hierarchy.

They all follow the same pattern:

 public class RootNode
 {
     private readonly List<SubNode> _subList = new List<SubNode>();
     public ERootType RootType { get; set; }
     public IEnumerable<SubNode> Subs => _subList;

     public RootNode(ERootType rootType)
     {
         RootType = rootType;
     }

     public void AddSub(SubNode subNodeToAdd)
     {
         _subList.Add(subNodeToAdd);
     }
 }

public class SubNode
{
    private readonly List<ItemNode> _itemList = new List<ItemNode>();
    public ESubType SubType { get; set; }
    public IEnumerable<ItemNode> Items => _itemList;

    public SubNode(ESubType subType)
    {
        SubType = subType;
    }

    public void AddItem(ItemNode itemToAdd)
    {
        _itemList.Add(itemToAdd);
    }
}

public class ItemNode
{
    private readonly List<Payload> _payloadList  = new List<Payload>();

    public string Item { get; set; }
    public IEnumerable<Payload> Payloads => _payloadList;
    public ItemNode(string item)
    {
        Item = item;
    }

    public void AddPayload(Payload payloadToAdd)
    {
        _payloadList.Add(payloadToAdd);
    }
}
  1. Then, right after the assignment of grouped3 in the original code, I added this.

It just walks the grouped3 mess and creates a bunch of nodes in a tree:

    var roots = new List<RootNode>();

    foreach (var root in grouped3)
    {
        var rootNode = new RootNode(root.Key.RootType);
        roots.Add(rootNode);
        foreach (var sub in root)
        {
            var subNode = new SubNode(sub.Key.SubType);
            rootNode.AddSub(subNode);
            foreach (var item in sub)
            {
                var itemNode = new ItemNode(item.Key.DisplayName);
                subNode.AddItem(itemNode);
                foreach (var payload in item)
                {
                    itemNode.AddPayload(payload.Payload as Payload);
                }
            }
        }
    }

When I look in the debugger, I see a strongly typed tree. Don't be afraid of anonymous types. The var keyword is your friend (well, that and the debugger).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM