我找不到或想出一个通用而优雅的算法,让我可以填充树状结构。 最简单的例子是博客档案:我有一堆记录可以按日期选择和排序。 我希望有一棵树,其中年份可能是最高级别,月份是下一级别,实际的帖子标题将再次出现。
到目前为止,我已经提出了一个天真的直接实现,但是我确信可以使用LINQ等进行改进。 在这里,我只按日期对记录进行排序,并迭代检查年份或月份是否已更改,并相应地添加树节点。 “BlogEntry”是一个引用父项和子项的类,后来用于生成HTML。
我欢迎有关改进算法的建议!
IEnumerable<Post> posts = db.Posts.OrderBy(p => p.DateCreated);
var topPost = posts.First();
int curYear = topPost.DateCreated.Year;
int curMonth = topPost.DateCreated.Month;
//create first "year-level" item
var topYear = new BlogEntry { Name = topPost.DateCreated.Year.ToString().ToLink(string.Empty) };
entries.Add(topYear);
var currentYear = topYear;
var topMonth = new BlogEntry { Name = topPost.DateCreated.ToString("MMMM").ToLink(string.Empty), Parent = currentYear };
currentYear.Children.Add(topMonth);
var currentMonth = topMonth;
foreach (var post in posts)
{
if(post.DateCreated.Year == curYear)
{
if (post.DateCreated.Month != curMonth)
{
//create "month-level" item
var month = new BlogEntry { Name = post.DateCreated.ToString("MMMM").ToLink(string.Empty), Parent = currentYear };
currentYear.Children.Add(month);
currentMonth = month;
curMonth = post.DateCreated.Month;
}
//create "blog entry level" item
var blogEntry = new BlogEntry { Name = post.Title.ToLink("/Post/" + post.PostID + "/" + post.Title.ToSeoUrl() ), Parent = currentMonth };
currentMonth.Children.Add(blogEntry);
}
else
{
//create "year-level" item
var year = new BlogEntry { Name = post.DateCreated.Year.ToString().ToLink(string.Empty) };
entries.Add(year);
currentYear = year;
curMonth = post.DateCreated.Month;
curYear = post.DateCreated.Year;
}
}
答案 0 :(得分:4)
我已经创建了一个测试示例,以检查逻辑的正确性。我认为这就是你所需要的。
public class BlogEntyTreeItem
{
public string Text { set; get; }
public string URL { set; get; }
public List<BlogEntyTreeItem> Children { set; get; }
public List<BlogEntyTreeItem> GetTree()
{
NWDataContext db = new NWDataContext();
var p = db.Posts.ToList();
var list = p.GroupBy(g => g.DateCreated.Year).Select(g => new BlogEntyTreeItem
{
Text = g.Key.ToString(),
Children = g.GroupBy(g1 => g1.DateCreated.ToString("MMMM")).Select(g1 => new BlogEntyTreeItem
{
Text = g1.Key,
Children = g1.Select(i => new BlogEntyTreeItem { Text = i.Name }).ToList()
}).ToList()
}).ToList();
return list;
}
}
答案 1 :(得分:1)
使用链接Playing with Linq grouping: GroupByMany ? 在How can I hierarchically group data using LINQ?
中建议我首先将我的代码重构为
解决方案1
var results = from allPosts in db.Posts.OrderBy(p => p.DateCreated)
group allPosts by allPosts.DateCreated.Year into postsByYear
select new
{
postsByYear.Key,
SubGroups = from yearLevelPosts in postsByYear
group yearLevelPosts by yearLevelPosts.DateCreated.Month into postsByMonth
select new
{
postsByMonth.Key,
SubGroups = from monthLevelPosts in postsByMonth
group monthLevelPosts by monthLevelPosts.Title into post
select post
}
};
foreach (var yearPosts in results)
{
//create "year-level" item
var year = new BlogEntry { Name = yearPosts.Key.ToString().ToLink(string.Empty) };
entries.Add(year);
foreach (var monthPosts in yearPosts.SubGroups)
{
//create "month-level" item
var month = new BlogEntry { Name = new DateTime(2000, (int)monthPosts.Key, 1).ToString("MMMM").ToLink(string.Empty), Parent = year };
year.Children.Add(month);
foreach (var postEntry in monthPosts.SubGroups)
{
//create "blog entry level" item
var post = postEntry.First() as Post;
var blogEntry = new BlogEntry { Name = post.Title.ToLink("/Post/" + post.PostID + "/" + post.Title.ToSeoUrl()), Parent = month };
month.Children.Add(blogEntry);
}
}
}
然后进入更通用的
解决方案2
var results = db.Posts.OrderBy(p => p.DateCreated).GroupByMany(p => p.DateCreated.Year, p => p.DateCreated.Month);
foreach (var yearPosts in results)
{
//create "year-level" item
var year = new BlogEntry { Name = yearPosts.Key.ToString().ToLink(string.Empty) };
entries.Add(year);
foreach (var monthPosts in yearPosts.SubGroups)
{
//create "month-level" item
var month = new BlogEntry { Name = new DateTime(2000, (int)monthPosts.Key, 1).ToString("MMMM").ToLink(string.Empty), Parent = year };
year.Children.Add(month);
foreach (var postEntry in monthPosts.Items)
{
//create "blog entry level" item
var post = postEntry as Post;
var blogEntry = new BlogEntry { Name = post.Title.ToLink("/Post/" + post.PostID + "/" + post.Title.ToSeoUrl()), Parent = month };
month.Children.Add(blogEntry);
}
}
}
................................................
public static class MyEnumerableExtensions
{
public static IEnumerable<GroupResult> GroupByMany<TElement>(
this IEnumerable<TElement> elements,
params Func<TElement, object>[] groupSelectors)
{
if (groupSelectors.Length > 0)
{
var selector = groupSelectors.First();
//reduce the list recursively until zero
var nextSelectors = groupSelectors.Skip(1).ToArray();
return
elements.GroupBy(selector).Select(
g => new GroupResult
{
Key = g.Key,
Items = g,
SubGroups = g.GroupByMany(nextSelectors)
});
}
else
return null;
}
}
public class GroupResult
{
public object Key { get; set; }
public IEnumerable Items { get; set; }
public IEnumerable<GroupResult> SubGroups { get; set; }
}