【问题标题】:How to .GroupBy() by Id and by list property?如何按 ID 和列表属性 .GroupBy()?
【发布时间】:2020-11-18 09:55:41
【问题描述】:

我有这些课程:

public class AlertEvaluation
{
    public string AlertId { get; set; }
    public ICollection<EvaluatedTag> EvaluatedTags { get; set; }
    public string TransactionId { get; set; }
    public EvaluationStatus EvaluationStatus { get; set; }
    public DateTime EvaluationDate { get; set; }
}

public class EvaluatedTag
{
    public string Id { get; set; }
    public string Name { get; set; }
}

我想获得按AlertIdEvaluatedTags 分组的警报评估列表,这意味着我想比较和分组评估不仅具有相同的AlertId,而且还具有EvaluatedTags 的相同列表。 (并且还能及时得到最后的评价)

我试过这个:

var evaluationsGroupedAndOrdered = evaluations.GroupBy(x => new { x.AlertSettingId, x.EvaluatedLabels })
.Select(x => x.OrderByDescending(z => z.EvaluationDate ).FirstOrDefault()).ToList();

当然,这样的列表属性比较是行不通的。

我在GroupBy 中阅读了有关添加相等比较器的内容,这意味着比较对象内的列表对吗?但我不确定如何以正确的方式实现它。

我试过了(基于GroupBy on complex object (e.g. List<T>)):

        public class AlertEvaluationComparer : IEqualityComparer<AlertEvaluation>
    {
        public bool Equals(AlertEvaluation x, AlertEvaluation y)
        {
            return x.AlertId == y.AlertId && x.EvaluatedTags.OrderBy(val => val.Name).SequenceEqual(y.EvaluatedTags.OrderBy(val => val.Name));
        }

        public int GetHashCode(AlertSettingEvaluation x)
        {
            return x.AlertId.GetHashCode() ^ x.EvaluatedTags.Aggregate(0, (a, y) => a ^ y.GetHashCode());
        }
    }

但也没有用。也许是因为我的 EvaluatedTags 列表不是字符串列表,而是单个对象的列表。

有人对此有很好的解决方案吗?

【问题讨论】:

  • 你确定GroupBy(x =&gt; x.AlertId, x.EvaluatedTags) 会编译吗?它应该是GroupBy(x =&gt; new { x.AlertId, x.EvaluatedTags})GroupBy(x =&gt; (x.AlertId, x.EvaluatedTags))
  • GroupBy on complex object (e.g. List<T>) 似乎是完全重复的
  • EvaluatedTag方法中覆盖Equals()GetHashcode()可能会简化linq代码。
  • @PavelAnikhouski,stackoverflow.com/questions/35128996/… 的解决方案对我不起作用。显示的列表中有一个字符串列表,它在 GetHashCode 方法中获取这些字符串的哈希码。我的列表是对象列表
  • @RufusL 是的,但是我怎样才能以正确的方式实现 Equals() 和 GetHashcode() 呢?

标签: c# .net linq group-by iequalitycomparer


【解决方案1】:

比较两个列表的典型方法是使用System.Linq 扩展方法SequenceEquals。如果两个列表以相同的顺序包含相同的项目,则此方法返回 true。

为了使用IEnumerable&lt;EvaluatedTag&gt;,我们需要有一种方法来比较EvaluatedTag 类的实例是否相等(确定两个项目是否相同)和排序(因为列表需要将它们的项目按相同的顺序排列)。

为此,我们可以覆盖EqualsGetHashCode 并实现IComparable&lt;EvaluatedTag&gt;(为了完整性,也可以使用IEquatable&lt;EvaluatedTag&gt;):

public class EvaluatedTag : IEquatable<EvaluatedTag>, IComparable<EvaluatedTag>
{
    public string Id { get; set; }
    public string Name { get; set; }

    public int CompareTo(EvaluatedTag other)
    {
        if (other == null) return -1;
        var result = string.CompareOrdinal(Id, other.Id);
        return result == 0 ? string.CompareOrdinal(Name, other.Name) : result;
    }

    public bool Equals(EvaluatedTag other)
    {
        return other != null &&
               string.Equals(other.Id, Id) &&
               string.Equals(other.Name, Name);
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as EvaluatedTag);
    }

    public override int GetHashCode()
    {
        return Id.GetHashCode() * 17 +
               Name.GetHashCode() * 17;
    }
}

现在我们可以在您的问题中使用的自定义比较器中使用它,对EvaluatedTags 进行排序和比较:

public class AlertEvaluationComparer : IEqualityComparer<AlertEvaluation>
{
    // Return true if the AlertIds are equal, and the EvaluatedTags 
    // contain the same items (call OrderBy to ensure they're in 
    // the same order before calling SequenceEqual).
    public bool Equals(AlertEvaluation x, AlertEvaluation y)
    {
        if (x == null) return y == null;
        if (y == null) return false;
        if (!string.Equals(x.AlertId, y.AlertId)) return false;
        if (x.EvaluatedTags == null) return y.EvaluatedTags == null;
        if (y.EvaluatedTags == null) return false;
        return x.EvaluatedTags.OrderBy(et => et)
            .SequenceEqual(y.EvaluatedTags.OrderBy(et => et));
    }

    // Use the same properties in GetHashCode that were used in Equals
    public int GetHashCode(AlertEvaluation obj)
    {
        return obj.AlertId?.GetHashCode() ?? 0 * 17 +
               obj.EvaluatedTags?.Sum(et => et.GetHashCode() * 17) ?? 0;
    }
}

最后我们可以将您的AlertEvaluationComparer 传递给GroupBy 方法来对我们的项目进行分组:

var evaluationsGroupedAndOrdered = evaluations
    .GroupBy(ae => ae, new AlertEvaluationComparer())
    .OrderBy(group => group.Key.EvaluationDate)
    .ToList();

【讨论】:

    【解决方案2】:

    试一试,稍微远离 Linq,以便在利用排序的同时更轻松地一次构建一个组:

    // Build groups by using a combination of AlertId and EvaluatedTags hashcode as group key
    var groupMap = new Dictionary<string, SortedSet<AlertEvaluation>>();
    foreach (var item in evals)
    {
        var combinedKey = item.AlertId + EvaluatedTag.GetCollectionHashCode(item.EvaluatedTags);
        if (groupMap.TryGetValue(combinedKey, out SortedSet<AlertEvaluation>? groupItems))
        {
            // Add to existing group
            groupItems.Add(item);
        }
        else
        {
            // Create new group
            groupMap.Add(combinedKey, new SortedSet<AlertEvaluation> { item });
        }
    }
    
    // Get a list of groupings already sorted ascending by EvaluationDate
    List<SortedSet<AlertEvaluation>>? groups = groupMap.Values.ToList();
    

    这假设类实现了 IComparable 和 Equals/GetHashCode 以方便排序:

    public class AlertEvaluation : IComparable<AlertEvaluation>
    {
        public string AlertId { get; set; }
        public ICollection<EvaluatedTag> EvaluatedTags { get; set; }
        public string TransactionId { get; set; }
        public EvaluationStatus EvaluationStatus { get; set; }
        public DateTime EvaluationDate { get; set; }
    
        // Used by SortedSet
        public int CompareTo(AlertEvaluation? other)
        {
            if (other is null)
            {
                return 1;
            }
    
            return EvaluationDate.CompareTo(other.EvaluationDate);
        }
    }
    
    public class EvaluatedTag : IEquatable<EvaluatedTag?>
    {
        public string Id { get; set; }
        public string Name { get; set; }
    
        public bool Equals(EvaluatedTag? other) => other != null && Id == other.Id && Name == other.Name;
    
        public override int GetHashCode() => HashCode.Combine(Id, Name);
    
        // Helper to get a hash of item collection
        public static int GetCollectionHashCode(ICollection<EvaluatedTag> items)
        {
            var code = new HashCode();
            foreach (var item in items.OrderBy(i => i.Id))
            {
                code.Add(item);
            }
            return code.ToHashCode();
        }
    }
    

    顺便说一句,我在 .NET Core 中使用了新奇的 HashCode 类来覆盖哈希码。

    【讨论】:

      猜你喜欢
      • 2017-06-30
      • 1970-01-01
      • 2022-10-12
      • 2019-11-20
      • 2021-04-15
      • 2010-10-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多