【问题标题】:Creating a custom equality comparer for IEnumerables<T> when T is IEnumerable当 T 为 IEnumerable 时,为 IEnumerables<T> 创建自定义相等比较器
【发布时间】:2018-02-19 08:15:45
【问题描述】:

我想要一个自定义相等比较器IEnumerables。使用 @PaulZahracode,我创建了以下类:

class CustomEqualityComparer<T> : IEqualityComparer<IEnumerable<T>>
{
    public bool Equals(IEnumerable<T> x, IEnumerable<T> y)
    {
        var enumerables = new Dictionary<T, uint>();

        foreach (T item in x)
        {
            enumerables.Add(item, 1);
        }

        foreach (T item in y)
        {
            if (enumerables.ContainsKey(item))
            {
                enumerables[item]--;
            }
            else
            {
                return false;
            }
        }

        return enumerables.Values.All(v => v == 0);
    }

    public int GetHashCode(IEnumerable<T> obj) => obj.GetHashCode();
}

问题是如果T 本身是IEnumerable,那么ContainsKey 将检查引用相等,而这个相等比较器的重点是检查值相等在任何给定深度强>.

我想改用.Keys.Contains(),因为它可以接受IEqualityComparer 作为参数:

if (enumerables.Keys.Contains(item, this)) // not sure if "this" or a new object

但我收到以下错误(CS1929):

'Dictionary.KeyCollection' 不包含'Contains' 的定义,并且最佳扩展方法重载'Queryable.Contains(IQueryable, T, IEqualityComparer)' 需要'IQueryable' 类型的接收器

我不确定如何处理这个问题。如何解决?谢谢。


编辑:请注意,此比较器不关心顺序。

【问题讨论】:

  • 旁注:多次迭代IEnumerable 的成本可能非常高(即每次都读取一个大文件中的所有行) - 确保您了解限制并确实需要它。
  • @AlexeiLevenkov 谢谢!我还能如何比较它们?
  • 不应该Equals公开吗?
  • @FaizanRabbani - 是的。已编辑,谢谢。
  • 你的GetHashcode 是错误的,它不会为相等的枚举返回相同的哈希

标签: c# iequalitycomparer


【解决方案1】:
  • 正如其他人所提到的,IEnumerable&lt;T&gt; 可以永远枚举,因此在该接口上执行此操作很危险。我建议改用ICollection&lt;T&gt;——它有一个固定的大小。而且您会发现它适用于您想要使用的大多数类型。

  • 此外,我建议您使用TryGetValue 来减少您需要查字典的次数。

  • 您的代码未正确保留第一个可枚举项中每个项目的计数。

  • GetHashCode 需要考虑到可枚举的每个成员。

话虽如此,这里是对您的实施的调整:

class CustomEqualityComparer<T> : IEqualityComparer<ICollection<T>>
{
    public bool Equals(ICollection<T> x, ICollection<T> y)
    {
        if (x.Count != y.Count) return false;
        var enumerables = new Dictionary<T, uint>(x.Count);

        foreach (T item in x)
        {
            enumerables.TryGetValue(item, out var value);
            enumerables[item] = value + 1;
        }

        foreach (T item in y)
        {
            var success = enumerables.TryGetValue(item, out var value);
            if (success)
            {
                enumerables[item] = value - 1;
            }
            else
            {
                return false;
            }
        }

        return enumerables.Values.All(v => v == 0);
    }

    public int GetHashCode(ICollection<T> obj)
    {
         unchecked
         {
             var hashCode = 0;

             foreach(var item in obj)
             {
                 hashCode += (item != null ? item.GetHashCode() : 0);                
             }
             return hashCode;
         }

     }
}

【讨论】:

  • 感谢您的评论!问题是 success 是假的,即使值确实具有相等的值。
  • @Sipo 好的,我没有意识到你的T 也是IEnumerable,你需要为这种情况添加特定的处理。尽管我认为更重要的问题是您以这种格式使用的是哪种数据?
  • 谢谢。这个案例是我问题的核心,如果您能向我解释如何处理它,我将不胜感激。我有一个名为WorkSession 的类,具有EntranceExit 属性。该时间代表员工工作的一段时间。一个员工一天可以有多个WorkSession,所以我有一个字典,其中键是星期几,值是WorkSession 的列表。这本字典表示员工每周应该在什么时候上班。希望我很清楚。
【解决方案2】:

如果T 是可枚举的,则要拥有这样的递归比较器,您只需将适当的比较器传递给Dictionary。我认为getting type T from IEnumerable<T> 然后相当于new Dictionary&lt;U, uint&gt;(new CustomEqualityComparer&lt;U&gt;)(使用Create instance of generic type?)应该可以实现您想要的。

注意事项:

  • 如果您对任何字典/HashSet 使用比较器,则必须提供与Equals 匹配的GetHashCode 的正确实现。序列的默认Equals 是与您的Equals 不一致的参考比较。请注意,GetHashCode 的大多数实现取决于集合中项目的顺序 - 因此您需要找到一个适用于集合的项目。 IE。每个项目的哈希码总和就可以了,可能会使分布稍微变差。
  • 您可能希望使用 LINQ 设置操作而不是手动操作。像Distinct 这样的所有操作都已经有了比较器。如果“集合相同”,您可以使用Distinct - x.Distinct(y, comparerBuiltViaReflection)
  • 注意此类代码的限制:并非每个可枚举项都可以多次枚举(用户输入、网络流等),或者可能在重新迭代时产生不同的结果 (while(count &lt; 10){ count ++; yield return random.Next(); }),重新迭代的成本很多重要(在每次迭代中重新读取大文件中的所有行)或可枚举可以表示无限序列 (while(true){ yield return count++; })。

【讨论】:

  • 谢谢!您能否提供一个代码示例,说明如何使用 LINQ 完成此操作?
  • @Sipo 我已经添加了所有问题的链接,您需要构建内部比较器并链接到Distinct
  • 谢谢。我曾尝试实施您的建议,但失败了。你能给我看一些实际的代码吗?我不知道如何解决这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-27
  • 1970-01-01
  • 2012-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多