C# LINQ - 按属性分组列表,然后按不同组选择

Posted

技术标签:

【中文标题】C# LINQ - 按属性分组列表,然后按不同组选择【英文标题】:C# LINQ - Group List by a property then select by different groups 【发布时间】:2021-09-06 07:46:31 【问题描述】:

我有一个要按属性A 分组的列表。然后选择GroupBy 键为null'' 的该组的所有元素,然后我想返回这些组的所有元素。否则,对于组键,返回该组的第一个元素。我想出了以下内容以及我对代码的期望。任何帮助将不胜感激。

示例代码:

public class ABC

    public string A  get; set; 
    public string B  get; set; 
    public string C  get; set; 


var list = new List<ABC>()

    new ABC()  A = "a", B = "b", C = "c" ,
    new ABC()  A = "a", B = "b", C = "c" ,
    new ABC()  A = null, B = "b", C = "c" ,
    new ABC()  A = null, B = "b", C = "c" ,
    new ABC()  A = "", B = "b", C = "c" ,
    new ABC()  A = "", B = "b", C = "c" ,
;

var result = list.GroupBy(x => x.A)
                 .Select(x => 
                 
                     if (!string.IsNullOrEmpty(x.Key))
                        return x.First();
                     else
                        return x;
                 )
                 .ToList();

我的期望:

我希望从结果中看到5 的总数

 A = "a", B = "b", C = "c"  count 1 (1 removed)
 A = null, B = "b", C = "c"  count 2 (none removed)
 A = "", B = "b", C = "c"  count 2 (none removed)

【问题讨论】:

在您的Select 中,您可以将其修改为返回列表(其中某些列表仅包含一项),例如:var result = list.GroupBy(x =&gt; x.A).Select(x =&gt; string.IsNullOrEmpty(x.Key) ? x.ToList() : new List&lt;ABC&gt; x.First() ).ToList(); 【参考方案1】:

类似这样的:

 var result = list
   .GroupBy(item => item.A)
   .SelectMany(group => string.IsNullOrEmpty(group.Key)
      ? group as IEnumerable<ABC>
      : new ABC[] group.First())
   .ToList();

这里的诀窍是我们应该返回collection:要么是整个组,要么是一个仅包含First 项的集合(数组)。

您可以摆脱 groupingflatten 以获得更快的版本,但是会利用 副作用

 HashSet<string> uniqueA = new HashSet<string>(); 

 var result = list
   .Where(item => string.IsNullOrEmpty(item.A) || uniqueA.Add(item.A))
   .ToList(); 

【讨论】:

【参考方案2】:
var result = list.GroupBy(x => x.A)
                .Select(g =>
                    new
                    
                        Key = g.Key,
                        Values = string.IsNullOrEmpty(g.Key) ? g.ToList() : new List<ABC> g.FirstOrDefault()
                    )
                .ToList();

【讨论】:

【参考方案3】:

你必须使用 LINQ 吗? @pwilcox 的答案很好,但您可能会发现这样的内容更具可读性:

var newList = new List<ABC>();
var setOfA = new HashSet<string>();
        
foreach (var abc in list)

    if (string.IsNullOrEmpty(abc.A))
    
        newList.Add(abc);
    
    else
    
        if (setOfA.Contains(abc.A))
        
            continue;
        

        setOfA.Add(abc.A);
        newList.Add(abc);
    

【讨论】:

【参考方案4】:

几点建议:

可选,但考虑使用三元运算符而不是 if/else。 如果IsNullOrEmpty 为假,则不要使用First,而是使用Where 并过滤“0”的索引,以便它保持列表形式,即使其中只有一项。 使用SelectMany 而不是Select 来展平结果

以下是实际操作中的指针:

var result = 
    list
    .GroupBy(x => x.A)
    .SelectMany(x => 
        string.IsNullOrEmpty(x.Key)
        ? x
        : x.Where((val,ix) => ix == 0)
    )
    .ToList();

使用您的示例数据生成的 LINQPad 转储:

A B C
a b c
null b c
null b c
b c
b c

【讨论】:

以上是关于C# LINQ - 按属性分组列表,然后按不同组选择的主要内容,如果未能解决你的问题,请参考以下文章

C# LINQ NET 3.5 SP1:使用 LINQ 按两个字段分组,并为组的所有成员分配一个相关的唯一 ID(整数)

C# 属性列表。需要根据2个条件对它们进行分组[重复]

C# - LINQ - 按日期范围分组集合

LINQ - 按多个键分组未给出预期结果

LINQ,按属性分组,同时保持其他属性排序

C# Unity 控制BUTTON不能按