用于生成层次结构的 C# 算法
Posted
技术标签:
【中文标题】用于生成层次结构的 C# 算法【英文标题】:C# algorithm for generating hierarchy 【发布时间】:2010-10-31 04:12:26 【问题描述】:我有一个如下所示的文本文件:
Id = 1, ParentId = 0, Position = 0, Title = "root"
Id = 2, ParentId = 1, Position = 0, Title = "child 1"
Id = 3, ParentId = 1, Position = 1, Title = "child 2"
Id = 4, ParentId = 1, Position = 2, Title = "child 3"
Id = 5, ParentId = 4, Position = 0, Title = "grandchild 1"
我正在寻找一种通用的 C# 算法,该算法将以此创建对象层次结构。如果你愿意的话,一个“层次化”功能可以将这些数据转换成一个对象层次结构。
有什么想法吗?
edit 我已经将文件解析为 .NET 对象:
class Node
public int Id get;
public int ParentId get;
public int Position get;
public string Title get;
现在我需要将对象实际排列到对象图中。
【问题讨论】:
你是否已经有了处理这个文本文件解析的代码? 我看不出是什么让 item Id = 5 ... 成为孙子。孙子应该有一个孩子作为其父母,但它与所有其他孩子具有相同的父母。它的 ParentId 不应该是 2、3 或 4 吗?我也不清楚你需要什么“职位”。也许它是指以从左到右的方式对孩子进行排序,您需要明确指定吗? 我会假设 position 属性对每个父母的孩子进行排序。 帮助,我的错,这是一个错字。我已经更新了帖子。 pbz,是的,我已经有了处理文件解析的代码。 【参考方案1】:非常感谢 Jon 和 mquander - 你们给了我足够的信息来帮助我以适当、通用的方式解决这个问题。这是我的解决方案,一个将对象转换为层次形式的通用扩展方法:
public static IEnumerable<Node<T>> Hierarchize<T, TKey, TOrderKey>(
this IEnumerable<T> elements,
TKey topMostKey,
Func<T, TKey> keySelector,
Func<T, TKey> parentKeySelector,
Func<T, TOrderKey> orderingKeySelector)
var families = elements.ToLookup(parentKeySelector);
var childrenFetcher = default(Func<TKey, IEnumerable<Node<T>>>);
childrenFetcher = parentId => families[parentId]
.OrderBy(orderingKeySelector)
.Select(x => new Node<T>(x, childrenFetcher(keySelector(x))));
return childrenFetcher(topMostKey);
利用这个小节点类:
public class Node<T>
public T Value get; private set;
public IList<Node<T>> Children get; private set;
public Node(T value, IEnumerable<Node<T>> children)
this.Value = value;
this.Children = new List<Node<T>>(children);
它足够通用,可以解决各种问题,包括我的文本文件问题。漂亮!
****更新****:这是您的使用方法:
// Given some example data:
var items = new[]
new Foo()
Id = 1,
ParentId = -1, // Indicates no parent
Position = 0
,
new Foo()
Id = 2,
ParentId = 1,
Position = 0
,
new Foo()
Id = 3,
ParentId = 1,
Position = 1
;
// Turn it into a hierarchy!
// We'll get back a list of Node<T> containing the root nodes.
// Each node will have a list of child nodes.
var hierarchy = items.Hierarchize(
-1, // The "root level" key. We're using -1 to indicate root level.
f => f.Id, // The ID property on your object
f => f.ParentId, // The property on your object that points to its parent
f => f.Position, // The property on your object that specifies the order within its parent
);
【讨论】:
你能举个例子如何使用它吗? @Baran 确定。我添加了示例用法。 这甚至适用于动态对象!正是我需要的!谢谢!【参考方案2】:嗯...我不太明白它是如何工作的。 2 和 5 怎么会有 parent=1,position=0? 5 应该有父 2、3 还是 4?
好的,这个新版本遍历所有节点三遍:
加载所有节点并将它们放入 a 地图中 将每个节点与其父节点关联 按位置对每个节点的子节点进行排序它没有很好地封装,很好地检查错误等 - 但它可以工作。
using System;
using System.Collections.Generic;
using System.IO;
public class Node
private static readonly char[] Braces = "".ToCharArray();
private static readonly char[] StringTrim = "\" ".ToCharArray();
public Node Parent get; set;
public int ParentId get; private set;
public int Id get; private set;
public string Title get; private set;
public int Position get; private set;
private readonly List<Node> children = new List<Node>();
public List<Node> Children get return children;
public static Node FromLine(string line)
Node node = new Node();
line = line.Trim(Braces);
string[] bits = line.Split(',');
foreach (string bit in bits)
string[] keyValue = bit.Split('=');
string key = keyValue[0].Trim();
string value = keyValue[1].Trim();
switch (key)
case "Id":
node.Id = int.Parse(value);
break;
case "ParentId":
node.ParentId = int.Parse(value);
break;
case "Position":
node.Position = int.Parse(value);
break;
case "Title":
node.Title = value.Trim(StringTrim);
break;
default:
throw new ArgumentException("Bad line: " + line);
return node;
public void Dump()
int depth = 0;
Node node = this;
while (node.Parent != null)
depth++;
node = node.Parent;
Console.WriteLine(new string(' ', depth * 2) + Title);
foreach (Node child in Children)
child.Dump();
class Test
static void Main(string[] args)
var dictionary = new Dictionary<int, Node>();
using (TextReader reader = File.OpenText("test.txt"))
string line;
while ((line = reader.ReadLine()) != null)
Node node = Node.FromLine(line);
dictionary[node.Id] = node;
foreach (Node node in dictionary.Values)
if (node.ParentId != 0)
node.Parent = dictionary[node.ParentId];
node.Parent.Children.Add(node);
foreach (Node node in dictionary.Values)
node.Children.Sort((n1, n2) =>
n1.Position.CompareTo(n2.Position));
Node root = dictionary[1];
root.Dump();
示例文本文件:
Id = 5, ParentId = 4, Position = 0, Title = "grandchild 1"
Id = 2, ParentId = 1, Position = 0, Title = "child 1"
Id = 4, ParentId = 1, Position = 2, Title = "child 3"
Id = 3, ParentId = 1, Position = 1, Title = "child 2"
Id = 1, ParentId = 0, Position = 0, Title = "root"
输出:
root
child 1
child 2
child 3
grandchild 1
【讨论】:
乔恩,您的代码不会读取文本文件。您神奇地将文本文件(数据)转换为源代码。那种消除或忽略了一半的问题。 @Cheeso:这就是为什么我要求你在问题的 cmets 中指定是否也需要这个。解析文本文件是一个完全不同的问题,对我来说,这个洞闻起来像家庭作业。 @Cheeso:我以为你可以做到这一点。文本文件真的是那种格式吗?字符串是如何转义的?我将编辑答案以使其完全解析问题中的格式,但我们确实需要更多信息。 好的,我已经放入了一个 very 原始解析器,它假设没有什么有趣的东西,比如字符串转义等。我仍然忽略了 Position,假设事情会在正确的顺序,我仍然假设父母在孩子之前。哦,我仍然认为您提供的示例中有错字……但除此之外,它确实有效。 乔恩,我的错,这是一个错字。我已经更新了帖子,以便 ID 5 是正确的孙子。【参考方案3】:解析完文件后,您可以按照blog 了解如何使用 LINQ 将对象组装成层次结构。
【讨论】:
很有趣,但似乎 Omer Van Kloeten 的实现并不关心父母内部的排序。【参考方案4】:我假设您的示例错误地为对象 #5 提供了错误的父 ID。这应该涵盖它。注意事项:假设“最顶层”节点的父 ID 始终为零。忽略最终不是从最顶层节点下降的任何节点。如果出现重复 ID,行为会很奇怪。
public class FlatObj
public int Id;
public int ParentId;
public int Position;
public string Title;
public class Node
public int ID;
public string Title;
public IList<Node> Children;
public Node(FlatObject baseObject, IList<Node> children)
this.ID = baseObject.Id;
this.Title = baseObject.Title;
this.Children = children;
public static Node CreateHierarchy(IEnumerable<FlatObject> objects)
var families = objects.ToLookup(x => x.ParentId);
var topmost = families[0].Single();
Func<int, IList<Node>> Children = null;
Children = (parentID) => families[parentID]
.OrderBy(x => x.Position)
.Select(x => new Node(x, Children(x.Id))).ToList();
return new Node(topmost, Children(topmost.Id));
public static void Test()
List<FlatObj> objects = new List<FlatObj>
new FlatObj Id = 1, ParentId = 0, Position = 0, Title = "root" ,
new FlatObj Id = 2, ParentId = 1, Position = 0, Title = "child 1" ,
new FlatObj Id = 3, ParentId = 1, Position = 1, Title = "child 2" ,
new FlatObj Id = 4, ParentId = 1, Position = 2, Title = "child 3" ,
new FlatObj Id = 5, ParentId = 2, Position = 0, Title = "grandchild" ;
var nodes = CreateHierarchy(objects);
【讨论】:
我认为你在两个方面没有抓住重点。您专注于文本文件中某一行的 ParentId。让我们暂时假设原始问题中有错字。无论如何,问题仍然存在 - 如何从这种数据中水合对象图。您提供的答案使用看起来像文本文件的源代码。 ???这相当于一半的答案。你完全忽略了解析文件的问题。对您来说,这似乎很简单,但它并非微不足道。 我认为他的问题是假设文件已经被解析为类似于我的 FlatObj 的某个对象,并且他正在向我们展示文件内容的抽象表示。 mquander 是正确的,我的问题假设文本文件已经被解析为一些对象数据。我将更新问题以澄清这一点。 mquander,感谢您的解决方案,我会试一试。如果结果很好,我会将其标记为正确答案。【参考方案5】:class Node
public int Id get;set;
public int ParentId get;set;
public int Position get;set;
public string Title get;set;
public IEnumerable<Node> Children get; set;
public override string ToString() return ToString(0);
public string ToString(int depth)
return "\n" + new string(' ', depth * 2) + Title + (
Children.Count() == 0 ? "" :
string.Join("", Children
.Select(node => node.ToString(depth + 1))
.ToArray()
);
class Program
static void Main(string[] args)
var data = new[]
new Node Id = 1, ParentId = 0, Position = 0, Title = "root" ,
new Node Id = 2, ParentId = 1, Position = 0, Title = "child 1" ,
new Node Id = 3, ParentId = 1, Position = 1, Title = "child 2" ,
new Node Id = 4, ParentId = 1, Position = 2, Title = "child 3" ,
new Node Id = 5, ParentId = 3, Position = 0, Title = "grandchild 1"
;
Func<Node, Node> transform = null;
transform = node => new Node
Title = node.Title,
Id = node.Id,
ParentId = node.ParentId,
Position = node.Position,
Children = (
from child in data
where child.ParentId == node.Id
orderby child.Position
select transform(child))
;
Console.WriteLine(transform(data[0]));
结果:
root
child 1
child 2
grandchild 1
child 3
【讨论】:
【参考方案6】:你确定最后一行的 ParentID 是 1 吗?标题是孙子,但如果我没看错的话,它会是“根”的孩子。
【讨论】:
【参考方案7】:这是@baran 要求的示例:
var lHierarchicalMenuItems = lMenuItemsFromDB.Hierarchize(0, aItem => aItem.Id, aItem => aItem.ParentId, aItem => aItem.Position);
【讨论】:
以上是关于用于生成层次结构的 C# 算法的主要内容,如果未能解决你的问题,请参考以下文章