【问题标题】:LINQ Distinct counting duplicates even when the hash codes are the same?即使哈希码相同,LINQ也会重复计数?
【发布时间】:2014-06-03 05:22:27
【问题描述】:

我正在按 RegEx 模式对日志记录进行分组。将它们分组后,我想获得每个组的记录的Distinct 计数。对于本例,Distinct 定义为相同的访问键和相同的年、月、日、小时和分钟。

这只是一种更准确地计算不同消费者在堆栈中一直记录的东西的方法。

好的,所以我将它们分组如下:

var knownMessages = logRecords
    .Where(record => !string.IsNullOrEmpty(record.InclusionPattern))
    .GroupBy(record => new
    {
        MessagePattern = record.InclusionPattern
    })
    .Select(g => new KnownMessage
    {
        MessagePattern = g.Key.MessagePattern,
---->   Count = g.Distinct().Count(),
        Records = g.ToList()
    })
    .OrderByDescending(o => o.Count);

GetHashCode 的类型是这样实现的:

public override int GetHashCode()
{
    var visitKeyHash = this.VisitKey == null ?
        251 : this.VisitKey.GetHashCode();
    var timeHash = this.Time.Year + this.Time.Month + this.Time.Day +
        this.Time.Hour + this.Time.Minute;

    return ((visitKeyHash * 251) + timeHash) * 251;
}

但是,例如,在列表中,我有三个记录返回相同的哈希码1439926797;我仍然得到3 的计数。我知道它正在利用GetHashCode(正如我所料)进行比较,因为我在那里有一个断点来查看哈希码是什么。

我错过了什么?

【问题讨论】:

  • 首先,在一个不会导致冲突的庄园中将哈希值组合在一起并不是一个很好的方法。其次,你没有展示Equals的定义,这对于相等的定义当然是必不可少的。
  • @Servy,我认为如果哈希码匹配,它们将被视为相等。
  • 你应该实现 IEquitable
  • @MichaelPerrenoud 不。重要的是始终覆盖两者或都不覆盖,而不是只覆盖一个,并且无论何时覆盖两者都应该使用相同的“平等”一般定义,否则会发生坏事。跨度>
  • @MichaelPerrenoud 逻辑是:如果a.GetHashcode() != b.GetHashCode() 然后a != b,如果a.GetHashCode() == b.GetHashCode() && a.Equals(b) 然后a == b,所有GetHashcode() 为你做的是让你跳过Equals() 检查你是否有两个不同的值。这就是为什么你需要同时实现这两个,如果你只实现Equals(),那么a.GetHashCode() == b.GetHashCode() 步骤会失败,它永远不会尝试你实现的Equals()

标签: c# linq hash equality


【解决方案1】:

首先让我重复一下我在评论中所说的话。

逻辑是:如果a.GetHashcode() != b.GetHashCode() 然后a != b, 如果a.GetHashCode() == b.GetHashCode() && a.Equals(b) 然后a == b,所有GetHashcode() 为您做的是让您跳过Equals() 检查您是否有两个不同的值。这就是为什么您需要同时实现两者的原因,如果您只实现 Equals() 那么 a.GetHashCode() == b.GetHashCode() 步骤将失败并且它永远不会尝试您实现的 Equals() 。

GetHashCode() 应该很快,并且当它位于取决于它的值的集合中时,它的值不应该改变。因此,如果您将它们存储在 DictionaryHashSet 或类似文件中,请不要修改 VisitKeyTime

所以你需要做的就是:

public override int GetHashCode()
{
    var visitKeyHash = this.VisitKey == null ?
        251 : this.VisitKey.GetHashCode();
    var timeHash = this.Time.Year + this.Time.Month + this.Time.Day +
        this.Time.Hour + this.Time.Minute;

    return ((visitKeyHash * 251) + timeHash);
}

public override bool Equals(object obj)
{
    //Two quick tests before we start doing all the math.        
    if(Object.ReferenceEquals(this, obj))
        return true;

    KnownMessage message = obj as KnownMessage;
    if(Object.ReferenceEquals(message, null)))
        return false;

    return this.VisitKey.Equals(message.VisitKey) &&
           this.time.Year.Equals(message.Time.Year) &&
           this.time.Month.Equals(message.Time.Month) &&
           this.time.Day.Equals(message.Time.Day) &&
           this.time.Hour.Equals(message.Time.Hour) &&
           this.time.Minute.Equals(message.Time.Minute);
}

【讨论】:

  • +1。我会将Object.ReferenceEquals 的顺序更改为首先使用as 进行投射。而且我会使用if(Object.ReferenceEquals(message, null)) 而不是if(message == null),因为== 运算符可以重载,如果它再次调用Equals 将导致StackOverFlowException。这是很常见的错误:)
【解决方案2】:

你没有给你的Equals 覆盖。与DictionaryHashSet 等其他基于散列的集合一样,Distinct() 使用的内部结构使用GetHashCode() 选择要存储的散列,但Equals 用于确定实际相等性。

问题可能是您的 EqualsGetHashCode 中的错误,但在后一种情况下,它与您的 Equals 不正确匹配(GetHashCode 必须为两个对象返回相同的哈希值)其中Equals 返回true,但当然也可以为两个不同的对象返回相同的值),这使得它成为这对方法中的一个错误。因此,无论哪种方式,问题都直接或间接地在于您对 Equals 的覆盖。

【讨论】:

    【解决方案3】:

    您似乎没有覆盖Equals 方法以使用与哈希码生成算法相同的相等定义。由于这用于解决哈希冲突,因此两者始终保持同步很重要。

    【讨论】:

    • 太棒了!所以我应该从Equals 调用GetHashCode 只要类型实际上是相同的;对吗?
    • @MichaelPerrenoud 不,因为哈希码可能会发生冲突。相等的哈希码并不意味着相等的对象,除非您的类型的可能值少于 2^32 个(在这种情况下,有效的哈希意味着没有哈希冲突,您很好)。
    • 好的,所以我写了GetHashCode 以导致类似的对象发生碰撞。此外,根据@Scott 的评论,强制Equals 甚至被调用是必要的。那么我为什么不直接从Equals 方法调用GetHashCode 呢?当然假设它们是相同的类型。
    • @MichaelPerrenoud 同样,因为不同的对象可能具有相同的哈希码。所有相等的对象必须具有相同的哈希码,并非所有不相等的对象都有不相等的哈希码。只有 2^32 个可能的整数,但大多数类型的可能值还有很多,包括你的。
    • 您不能从Equals 调用GetHashCode,除非在极少数情况下所有可能的值都具有不同的哈希码,即使那样,这也可能是一个糟糕的实现。您的Equals 进行比较,以查看它们是否以您所说的方式相等,这意味着所讨论的类的相等(通常是一组跨不同领域的相等比较)。您的GetHashCode 会根据相同的标准生成一个数字。因此,如果x==y 表示x.GetHashCode() == y.GetHashCode(),但x.GetHashCode() == y.GetHashCode() 不保证x==y
    猜你喜欢
    • 1970-01-01
    • 2015-03-09
    • 1970-01-01
    • 1970-01-01
    • 2012-07-07
    • 1970-01-01
    • 1970-01-01
    • 2015-06-14
    • 2011-08-03
    相关资源
    最近更新 更多