web-dev-qa-db-ja.com

親子関係を再帰的にチェックしてツリータイプリストを作成するC#

ツリー構造で表現できるように、自身のリストを持つ1つのクラスがあります。

これらのクラスのフラットリストを取得しており、フラット化を解除したいと考えています。

public class Group
{
     public int ID {get;set;}

     public int? ParentID {get;set;}

     public List<Group> Children {get;set;}

}

次のことができるようにしたい

List<Group> flatList = GetFlatList() //I CAN ALREADY DO THIS
List<Group> tree = BuildTree(flatList);

親グループのIDプロパティに関連するParentID。それが明らかでない場合。

[〜#〜] edit [〜#〜]

なぜ単一のオブジェクトではなくリストを返すのかについて、いくつかの混乱があります。

アイテムのリストを持つUI要素を作成しています。各要素には子があります。したがって、初期リストにはルートノードがありません。これまでのところ、すべてのソリューションが機能していないようです。

これが意味することは、基本的にGroupクラスを使用したツリー型構造のリストが必要だということです。

20
TheJediCowboy

BuildTreeメソッドがList<Group>を返す理由がわからない-ツリーにはルートノードが必要なので、リストではなく単一のGroup要素を返すことを期待する必要があります。

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

使用法

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();
38
MarcinJuraszek

これを1行で行う方法は次のとおりです。

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

次のように呼び出すことができます。

BuildTree(flatList);

最後に、親がnullのノード(つまり、最上位ノード)を取得する場合、これを行うことができます:

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

また、拡張メソッドにしたい場合は、メソッドシグネチャにthisを追加するだけです。

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

その後、次のように呼び出すことができます。

var roots = flatList.BuildTree();
24
JLRishe

提案された解決策を試してみて、O(n ^ 2)の複雑さについて教えてくれることがわかりました。

私の場合(ツリーに約5万個のアイテムを組み込む必要があります)、まったく受け入れられませんでした。

複雑さO(n * log(n))[n回のgetById、getByIdはO(log(n))複雑さ]:

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 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>();
    }
}
8