带组的复杂 LINQ 排序

发布于 2024-12-03 21:04:54 字数 1227 浏览 2 评论 0原文

我试图根据以下(简化的)规则对项目列表进行排序:

我将每个项目都具有以下属性:

 Id (int), 
 ParentId (int?), 
 Name (string)

ParentID 是一个到 Id 的自连接外键。如果一个项目有 ParentId,那么父项目也将存在于列表中。

我需要对列表进行排序,以便所有具有父级的项目都立即出现在其父级之后。然后所有项目将按名称排序。

因此,如果我有以下内容:

 Id: 1, ParentId: null, Name: Pi
 Id: 2, ParentId: null, Name: Gamma
 Id: 11, ParentId: 1, Name: Charlie
 Id: 12, ParentId: 1, Name: Beta
 Id: 21, ParentId: 2, Name: Alpha
 Id: 22, ParentId: 2, Name: Omega

那么我希望它们按如下方式排序:

ID:2、21、22、1、12、11

目前我能想到的最好办法是先按名称排序,然后按 ParentId 分组,如下所示:

var sortedItems = itemsToSort.OrderBy(x=> x.Name).GroupBy(x=> x.ParentId);

我的起始计划如下:(在非功能状态代码)

var finalCollection = new List<Item>

var parentGroup = sortedItems.Where(si => si.Key == null);

foreach(parent in parentGroup)
{
   finalCollection.Add(parent);
   foreach(child in sortedItems.Where(si => si.Key == parent.Id)
   {
      finalCollection.Add(child);
   }
}

但是,parentGroup 不是

 IEnumerable<Item> 

,所以这不起作用。

我觉得有一种更简单、更简洁的方法可以实现这一目标,但目前它却让我困惑——任何人都可以帮忙吗?

I am trying to sort a list of items according to the following (simplified) rules:

I'll each item has the following properties:

 Id (int), 
 ParentId (int?), 
 Name (string)

ParentID is a self-join ForeignKey to Id. If an item has a ParentId, then the parent will also exist in the list.

I need to sort the list so that all items which have a parent, appear immediately after their parent. Then all items will be sorted by Name.

So if I had the following:

 Id: 1, ParentId: null, Name: Pi
 Id: 2, ParentId: null, Name: Gamma
 Id: 11, ParentId: 1, Name: Charlie
 Id: 12, ParentId: 1, Name: Beta
 Id: 21, ParentId: 2, Name: Alpha
 Id: 22, ParentId: 2, Name: Omega

Then I would want them sorted as follows:

Ids: 2, 21, 22, 1, 12, 11

At the moment the best I can come up with is to sort by Name first, and then Group by ParentId as follows:

var sortedItems = itemsToSort.OrderBy(x=> x.Name).GroupBy(x=> x.ParentId);

My starting plan was then as follows: (in non functioning code)

var finalCollection = new List<Item>

var parentGroup = sortedItems.Where(si => si.Key == null);

foreach(parent in parentGroup)
{
   finalCollection.Add(parent);
   foreach(child in sortedItems.Where(si => si.Key == parent.Id)
   {
      finalCollection.Add(child);
   }
}

However, parentGroup is not

 IEnumerable<Item> 

so this does not work.

I feel there is an simpler, more concise way of achieving this, but currently it's eluding me - can anyone help?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

在风中等你 2024-12-10 21:04:54

如果您只有两个级别,您可以这样做:

var lookup = itemsToSort.OrderBy(x => x.Name).ToLookup(x => x.ParentId, x => x);
var parents = lookup[null];
var sortedItems = parents.SelectMany(x => new[] { x }.Concat(lookup[x.Id]));

最初,项目按名称排序,确保稍后将它们分成组时它们保持排序。

然后创建一个查找表,允许通过 ParentId 进行查找。然后,使用 SelectMany 将通过 null ParentId 标识的父级与其子级连接起来,并使用查找表来查找子级。将父级插入到子级之前以获得所需的序列。

如果你想解决两层以上的一般情况,你需要使用递归。这是一种递归获取节点子树的方法:

IEnumerable<Item> GetSubtreeForParent(Item parent, ILookup<Int32?, Item> lookup) {
  yield return parent;
  foreach (var child in lookup[parent.Id])
    foreach (var descendant in GetSubtreeForParent(child, lookup))
      yield return descendant;
}

代码几乎与上面的简单情况相同:

var lookup = itemsToSort.OrderBy(x => x.Name).ToLookup(x => x.ParentId, x => x);
var parents = lookup[null];
var sortedItems = parents.SelectMany(x => GetSubtreeForParent(x, lookup));

通过使用递归 lambda,您甚至可以“内联”完成所有操作:

var lookup = itemsToSort.OrderBy(x => x.Name).ToLookup(x => x.ParentId, x => x);
// Declare Func to allow recursion.
Func<Int32?, IEnumerable<Item>> getSubTreeForParent = null;
getSubTreeForParent =
  id => lookup[id].SelectMany(x => new[] { x }.Concat(getSubTreeForParent(x.Id)));
var sortedItems = getSubTreeForParent(null);

If you only have two levels you can do it like this:

var lookup = itemsToSort.OrderBy(x => x.Name).ToLookup(x => x.ParentId, x => x);
var parents = lookup[null];
var sortedItems = parents.SelectMany(x => new[] { x }.Concat(lookup[x.Id]));

Intially items are sorted by name ensuring that when they are later split into groups they stay sorted.

Then a lookup table is created allowing to lookup by ParentId. The parents identified by having a null ParentId are then joined with their children using SelectMany and the lookup table is used to find the children. The parent is inserted before the children to get the desired sequence.

If you want to solve the general case with more than two levels you need to employ recursion. Here is a way to recursively get the substree for a node:

IEnumerable<Item> GetSubtreeForParent(Item parent, ILookup<Int32?, Item> lookup) {
  yield return parent;
  foreach (var child in lookup[parent.Id])
    foreach (var descendant in GetSubtreeForParent(child, lookup))
      yield return descendant;
}

The code is almost the same as the simpler case above:

var lookup = itemsToSort.OrderBy(x => x.Name).ToLookup(x => x.ParentId, x => x);
var parents = lookup[null];
var sortedItems = parents.SelectMany(x => GetSubtreeForParent(x, lookup));

By using a recursive lambda you can even do it all "inline":

var lookup = itemsToSort.OrderBy(x => x.Name).ToLookup(x => x.ParentId, x => x);
// Declare Func to allow recursion.
Func<Int32?, IEnumerable<Item>> getSubTreeForParent = null;
getSubTreeForParent =
  id => lookup[id].SelectMany(x => new[] { x }.Concat(getSubTreeForParent(x.Id)));
var sortedItems = getSubTreeForParent(null);
茶色山野 2024-12-10 21:04:54

据我了解您的问题,您希望按父名称(如果是父名称)然后按子名称(如果是子名称)对结果进行排序,但您希望所有子名称都出现在列表中各自的父名称之后。

这应该可以解决问题:

更新以解决@Martin Liversage提到的问题。

var query = from item in itemsToSort
            let parent = itemsToSort.Where(i => i.Id == item.ParentId).FirstOrDefault()
            //get the name of the item's parent, or the item itself if it is a parent
            let parentName = (parent != null) ? parent.Name : item.Name
            //get the name of the child (use null if the item isn't a child)
            let childName = (parent != null) ? item.Name : null
            orderby parentName, childName
            select item;

var finalCollection = query.ToList();

这是输出:

在此处输入图像描述

As I understand your question you want to order the results by parent name (if it's a parent) and then by child name (if it's a child), but you want all children to appear in the list after their respective parent.

This should do the trick:

Updated to address the issue that @Martin Liversage mentioned.

var query = from item in itemsToSort
            let parent = itemsToSort.Where(i => i.Id == item.ParentId).FirstOrDefault()
            //get the name of the item's parent, or the item itself if it is a parent
            let parentName = (parent != null) ? parent.Name : item.Name
            //get the name of the child (use null if the item isn't a child)
            let childName = (parent != null) ? item.Name : null
            orderby parentName, childName
            select item;

var finalCollection = query.ToList();

Here's the output:

enter image description here

如果没结果 2024-12-10 21:04:54

这可以通过以下方式实现:

list.Select(i => 
      new {Parent=list.Where(x => x.Id == i.ParentId).FirstOrDefault(), Item = i})
    .OrderBy(i => i.Parent == null ? i.Item.Name : i.Parent.Name + i.Item.Name)
    .Select(i => i.Item)

实例:http://rextester.com/rundotnet?code=WMEZ40628

输出为:

2
21
22
1
12
11

This can be achieved using:

list.Select(i => 
      new {Parent=list.Where(x => x.Id == i.ParentId).FirstOrDefault(), Item = i})
    .OrderBy(i => i.Parent == null ? i.Item.Name : i.Parent.Name + i.Item.Name)
    .Select(i => i.Item)

Live example: http://rextester.com/rundotnet?code=WMEZ40628

Output is:

2
21
22
1
12
11
左耳近心 2024-12-10 21:04:54

我会选择 DoctaJonez 的 答案 2 级。

它可以扩展到 n 层,如下所示:

Func<int?,Item> lookup = id => list.Where(i => i.Id == id).FirstOrDefault();

Func<Item,string> makeSortString = null;
makeSortString = i => i.ParentId == null ? i.Name : makeSortString(lookup(i.ParentId)) + i.Name;

list.OrderBy(makeSortString).ToList();

I'd go with DoctaJonez's answer for 2 level.

It can be extended to n levels like so:

Func<int?,Item> lookup = id => list.Where(i => i.Id == id).FirstOrDefault();

Func<Item,string> makeSortString = null;
makeSortString = i => i.ParentId == null ? i.Name : makeSortString(lookup(i.ParentId)) + i.Name;

list.OrderBy(makeSortString).ToList();
无所谓啦 2024-12-10 21:04:54

var parentGroup = sortedItems.Where(si => si.Key == null).ToList()

将使 parentGroup IEnumerable

你会在顶层失去懒惰,但我认为由于上下文,这很好。

This

var parentGroup = sortedItems.Where(si => si.Key == null).ToList()

will make parentGroup IEnumerable<Item> .

You will loose laziness on the top level, but i think it's fine due to the context.

太阳男子 2024-12-10 21:04:54

这个怎么样?

var lookup = items.ToLookup(x => x.ParentId);

Func<int?, IEnumerable<Item>> f =  null;
f = ni =>
    from a in lookup[ni].OrderBy(x => x.Name)
    from b in (new [] { a }).Concat(f(a.Id))
    select b;

然后要获取排序列表,请执行以下操作:

var sorted = f(null);

简单。 :-)

How about this?

var lookup = items.ToLookup(x => x.ParentId);

Func<int?, IEnumerable<Item>> f =  null;
f = ni =>
    from a in lookup[ni].OrderBy(x => x.Name)
    from b in (new [] { a }).Concat(f(a.Id))
    select b;

And then to get the sorted list do this:

var sorted = f(null);

Simple. :-)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文