ツリー構造で表現できるように、自身のリストを持つ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クラスを使用したツリー型構造のリストが必要だということです。
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();
これを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();
提案された解決策を試してみて、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>();
}
}