LINQ Except和自定义IEqualityComparer

19

我试图实现一个自定义比较器,用于比较两个字符串列表,并使用.Except() linq方法获取那些不在其中一个列表中的字符串。我之所以需要自定义比较器,是因为我需要进行“模糊”比较,即一个列表中的一个字符串可能嵌入到另一个列表中的字符串中。

我已经编写了以下比较器:

public class ItemFuzzyMatchComparer : IEqualityComparer<string>
{
    bool IEqualityComparer<string>.Equals(string x, string y)
    {
        return (x.Contains(y) || y.Contains(x));
    }

    int IEqualityComparer<string>.GetHashCode(string obj)
    {
        if (Object.ReferenceEquals(obj, null))
            return 0;
        return obj.GetHashCode();
    }
}

我调试代码时,唯一触发的断点是在GetHashCode()方法中,Equals()从未被触发。有什么想法吗?


对我来说,这是一个很好的练习。在我的情况下,我使用了public int GetHashCode(string obj) {return obj.ToLower().GetHashCode();},并且成功解决了问题。虽然你的问题已经有些年头了,但我在4年后也遇到了同样的问题。 - T.S.
3个回答

17
如果所有返回的哈希码都不同,它就永远不需要比较相等性。
基本上问题在于您的哈希和相等的概念非常不同。我不确定如何纠正这一点,但在您纠正之前,它肯定行不通。
您需要确保如果Equals(a, b)返回true,则GetHashCode(a) == GetHashCode(b)。(反过来不一定为真-哈希碰撞是可以接受的,尽管显然您希望尽可能少地发生碰撞。)

我开始觉得这是试图在预定义对象(即字符串)的集合上应用自定义比较的情况。如果我有两个自定义对象的集合,那么我可能可以让它工作。我想我必须想出一个比那更好的方法。:( 我会把这个问题留待一天,看看是否有其他人有建议。 - Joe
我最终分两步完成了它。我使用contains生成了一个部分匹配的项目列表,然后再次使用该子集对第一个列表进行了排除。感谢您的帮助。 - Joe

6
正如Jon所指出的,您需要确保两个字符串的哈希码相等(根据您的比较规则)。不幸的是,这非常困难。
为了说明问题,Equals(str,"")对于所有字符串str都返回true,这基本上意味着所有字符串都等于空字符串,并且因此所有字符串必须具有与空字符串相同的哈希码。因此,实现IEqualityComparer的唯一正确方法是始终返回相同的哈希码:
public class ItemFuzzyMatchComparer : IEqualityComparer<string>  { 
  bool IEqualityComparer<string>.Equals(string x, string y)  { 
    return (x.Contains(y) || y.Contains(x)); 
  }  
  int IEqualityComparer<string>.GetHashCode(string obj)  { 
    if (Object.ReferenceEquals(obj, null)) return 0; 
    return 1; 
  } 
}

然后您可以使用 Except 方法并且它将正确地运行。唯一的问题是,您可能会得到一个相当低效的实现,因此如果需要更好的性能,则可能需要自己实现 Except。但是,我不确定 LINQ 实现的效率有多低,并且我不确定是否实际上可能对您的比较规则进行任何有效的实现。


1
也许可以不用实现IEqualityComparer接口来解决这个问题。Jon和Thomas提出了很好的观点,而且相等性似乎并没有定义你的问题。根据你的描述,我认为你可以在比较时不使用Except扩展,先获取匹配项,然后再进行Except操作。看看这是否能解决你的问题:
 List<String> listOne = new List<string>(){"hard", "fun", "code", "rocks"};
 List<String> listTwo = new List<string>(){"fund", "ode", "ard"};

 var fuzzyMatchList = from str in listOne
                      from sr2 in listTwo
                      where str.Contains(sr2) || sr2.Contains(str)
                      select str;
 var exceptList = listOne.Except(fuzzyMatchList);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接