Linq - Group然后比较每个组中的元素

时间:2016-04-06 15:08:53

标签: c# linq group-by

例如,假设在我的C#代码中,我有MyClass,定义为:

public class MyClass
{
    public string GroupName;
    public DateTime Dt;
    public int Id;
    public string Val;
    .... other properties ....
}

假设我有以下List<MyClass>(将其显示为表格,因为它似乎是描述内容的最简单方法):

GroupName:       Dt:             Id:        Val:
Group1           2016/01/01      1          Val1
Group1           2016/01/02      1          Val1
Group1           2016/01/03      1          Val1
Group1           2016/01/04      1          Val2
Group1           2016/01/05      1          Val3
Group1           2016/01/06      1          Val1
Group1           2016/01/07      1          Val1
Group1           2016/01/08      1          Val4
Group1           2016/01/09      1          Val4
显然,对于多个GroupName和不同Id s,会发生同样的事情。

我希望从此列表中获得的是,对于任何已命名的组,每个第一次更改值 - 因此Group1的输出将为:

Dt:             Id:        Val:
2016/01/01      1          Val1
2016/01/04      1          Val2
2016/01/05      1          Val3
2016/01/06      1          Val1
2016/01/08      1          Val4

换句话说,对于给定的GroupName

  1. 按ID分组
  2. 按日期排序
  3. 选择每个组中项目[index]!= item [index-1]
  4. 的任何项目

    所以,我得到了以下代码:

    public IEnumerable<MyClass> GetUpdatedVals(List<MyClass> myVals, string groupName)
    {
        var filteredVals = myVals.Where(v => v.GroupName == groupName).ToList();
    
        return filteredVals
            .OrderBy(v => v.Id)
            .ThenBy(v => v.Dt)
            .Where((v, idx) => idx == 0 || v.Id != filteredVals[idx - 1].Id || v.Val != filteredVals[idx - 1].Val)
            .Select(v => v);
    }
    

    但似乎应该有更好的方法通过Linq使用GroupBy或不必创建单独的保留列表。

    有什么想法吗?或者这是“非常好”/最佳方式?

    谢谢!

2 个答案:

答案 0 :(得分:2)

如果您想要更优雅的东西,可以使用https://stackoverflow.com/a/4682163/6137718中描述的GroupAdjacent功能:

public static class LinqExtensions
{
    public static IEnumerable<IEnumerable<T>> GroupAdjacentBy<T>(
        this IEnumerable<T> source, Func<T, T, bool> predicate)
    {
        using (var e = source.GetEnumerator())
        {
            if (e.MoveNext())
            {
                var list = new List<T> { e.Current };
                var pred = e.Current;
                while (e.MoveNext())
                {
                    if (predicate(pred, e.Current))
                    {
                        list.Add(e.Current);
                    }
                    else
                    {
                        yield return list;
                        list = new List<T> { e.Current };
                    }
                    pred = e.Current;
                }
                yield return list;
            }
        }
    }
}

我们可以使用它来按照Id和Dt排序,对具有相同Val的所有相邻元素进行分组。然后从每个组中选择第一个,因为它代表最近的变化。更新后的代码如下所示:

public IEnumerable<MyClass> GetUpdatedVals(List<MyClass> myVals, string groupName)
{
    return myVals
        .Where(v => v.GroupName == groupName)
        .OrderBy(v => v.Id)
        .ThenBy(v => v.Dt)
        .GroupAdjacentBy((x, y) => x.Val == y.Val && x.Id == y.Id)
        .Select(g => g.First());
}

答案 1 :(得分:1)

如果我理解您的要求和正确的工作代码,您希望获得所有更改。由于您已按ID排序,因此可以使用GroupBy获取ID - 群组。现在,您需要在Val - 值从一个对象更改为另一个对象的每个ID组中添加所有内容。您可以使用以下单个查询创建每个组的列表以通过索引访问前一个元素,并使用SelectMany来展平它们。

public IEnumerable<MyClass> GetUpdatedVals(List<MyClass> myVals, string groupName)
{
    return myVals
        .Where(v => v.GroupName == groupName)
        .OrderBy(v => v.Id)
        .ThenBy(v => v.Dt)
        .GroupBy(v => v.Id)
        .Select(g => g.ToList())
        .SelectMany(gList => gList
            .Where((v, idx) => idx == 0 || v.Val != gList[idx - 1].Val));
}