使用LINQ </object>从List <object>中删除重复项

时间:2014-05-03 09:51:19

标签: c# linq list

这个问题很简单,在SO中进行简单的搜索会提出一些类似的问题,但我仍然很难找到答案。

我想根据List<Column> columns的所有属性删除重复对象Column类本身具有List<string>属性,我认为我可能在GetHashCode()Equals部分遇到问题。

以下是我编写的完整代码,但我没有得到正确的结果。例如,在下面的代码中,我想删除column3,因为它在每个方面都与column1相同。

using System;
using System.Collections.Generic;
using System.Linq;

namespace Arrays
{
    public class ColumnList
    {
        public static void RemoveDuplicateColumnTypes()
        {
            // define columns
            var column1 = new Column
            {
                StartElevation = 0,
                EndElevation = 310,
                ListOfSections = new List<string> { "C50", "C40" }
            };
            var column2 = new Column
            {
                StartElevation = 0,
                EndElevation = 310,
                ListOfSections = new List<string> { "C50", "C30" }
            };
            var column3 = new Column
            {
                StartElevation = 0,
                EndElevation = 310,
                ListOfSections = new List<string> { "C50", "C40"}
            };

            // list of columns
            var columns = new List<Column> { column1, column2, column3 };

            var result = columns.Distinct(new ColumnListComparer());
        }
    }

    public class ColumnListComparer : IEqualityComparer<Column>
    {
        public bool Equals(Column x, Column y)
        {
            if (x == null || y == null) return false;

            if (Math.Abs(x.StartElevation - y.StartElevation) < 0.001 &&
                Math.Abs(x.EndElevation - y.EndElevation) < 0.001 &&
                x.ListOfSections.SequenceEqual(y.ListOfSections))
            {
                return true;
            }
            return false;
        }

        public int GetHashCode(Column obj)
        {
            return obj.StartElevation.GetHashCode() ^
                   obj.EndElevation.GetHashCode() ^
                   obj.ListOfSections.GetHashCode();
        }
    }

    public class Column
    {
        public double StartElevation { get; set; }
        public double EndElevation { get; set; }
        public List<string> ListOfSections { get; set; }
    }

}

5 个答案:

答案 0 :(得分:2)

更改您的GetHashCode,如下所示。

public int GetHashCode(Column obj)
{
    unchecked
    {
        var hashCode = obj.StartElevation.GetHashCode();
        hashCode = (hashCode * 397) ^ obj.EndElevation.GetHashCode();
        foreach (var item in obj.ListOfSections)
        {
            hashCode = (hashCode * 397) ^ item.GetHashCode();
        }
        return hashCode;
    }
}

您的版本不起作用,因为ListOfSections.GetHashCode是不确定的,可能会返回任何内容,因为它用于检查引用相等性,但我们在这里处理值相等。所以必须基于“值相等”生成哈希码

答案 1 :(得分:2)

这是罪魁祸首:

return obj.StartElevation.GetHashCode() ^
       obj.EndElevation.GetHashCode() ^
       obj.ListOfSections.GetHashCode(); // <<== This line

列表不会将其哈希码基于其元素的哈希码。具有相同元素的两个不同列表将不具有相同的哈希码。将此更改为聚合列表成员的哈希码的行,以使其工作:

return 31*31*obj.StartElevation.GetHashCode() +
       31*obj.EndElevation.GetHashCode() +
       obj.ListOfSections.Aggregate((p, v) => 31*p + v.GetHashCode());

注意:虽然这会使您的代码消失cell3您的代码将无效。原因不是那么明显 - 您将遇到的问题是您的Equals方法不具有传递性,因为您通过允许差异在公差0.001内被视为相等来比较双精度。这样做的结果是

Cell1 == Cell2 && Cell2 == Cell3

不再暗示

Cell1 == Cell3

这是根本错误的。

此外,这意味着您的算法认为相同的两个对象可能具有不同的哈希码。平等比较的合同禁止这样做。

为了解决此问题,请将代表高程切换为double,并使用intlong来存储以比单位小1000倍的单位表示的高程你现在有。换句话说,如果您的代码将高程存储为double 123.456,则新代码应存储整数123456。这可以让你比较平等与正确的容忍度。当您获得外部使用的高程时,将数字转换为double并除以1000以产生旧结果。

答案 2 :(得分:1)

列表的哈希码不匹配。所以整个哈希码不匹配。

试试这个:

 public int GetHashCode(Column obj)
 {
     return 42;
 }

如果对象相等,则哈希码必须相等。一般来说,返回固定数字并不理想,但它有效。为什么42?搭便车的人。

如果可行,那么你可以寻找一个更好的哈希函数,它实际上使用了列表中的一些值。

只要你遵循对象的黄金法则是相同的,你的哈希函数必须相等,你做散列函数的复杂程度取决于你。

这是一个有效的哈希函数,它只考虑列表的长度:

 public int GetHashCode(Column obj)
 {
    return obj.ListOfSections.Count;
 }

答案 3 :(得分:-1)

HashSet<T>只会添加唯一的商品。

List<T> withDupes = LoadSomeData();
List<T> noDupes = withDupes.Distinct().ToList();

答案 4 :(得分:-1)

可能你需要HasSet

在Column类中重写GetHashCode和Equals方法

将您的对象添加到HashSet,您将获得唯一的设置。

例如var columns = new HashSet(columnList);

以下是使用字符串

的示例
        public void ListToHashSet()
    {
        var list = new List<string> { "abc", "efg", "abc", "efg" };
        var set = new HashSet<string>(list);
        foreach (var item in list)
            Console.WriteLine("list:{0}", item);

        foreach (var item in set)
            Console.WriteLine("set:{0}", item);
    }

并检查输出,因为.net字符串类已经具有GetHashCode和Equals的覆盖,您不需要实现,但对于自定义类,您将需要覆盖两者,Gethashcode用于存储和从HashTable和GetEqual检索两个对象的平等。