如何创建HashSet <list <int>&gt;有不同的元素?</list <int>

时间:2011-04-01 19:28:45

标签: c# collections hashset distinct-values

我有一个包含多个整数列表的HashSet,即HashSet<List<int>>

为了保持独特性,我目前不得不做两件事: 1.手动循环现有列表,使用SequenceEquals查找重复项。 2.对单个列表进行排序,以便SequenceEquals当前正常工作。

有更好的方法吗?是否存在可以提供给HashSet的现有IEqualityComparer,以便HashSet.Add()可以自动处理唯一性?

var hashSet = new HashSet<List<int>>();

for(/* some condition */)
{
    List<int> list = new List<int>();

    ...

    /* for eliminating duplicate lists */

    list.Sort();

    foreach(var set in hashSet)
    {
        if (list.SequenceEqual(set))
        {
            validPartition = false;
            break;
        }
    }

    if (validPartition)
           newHashSet.Add(list);
}

谢谢!

5 个答案:

答案 0 :(得分:4)

这开始出错,它必须是HashSet<ReadOnlyCollection<>>,因为您不能允许列表更改并使设置谓词无效。然后,这允许您在将集合添加到集合时计算O(n)中的哈希码。并且进行O(n)测试以检查它是否已经在具有非常罕见的O(n ^ 2)最坏情况的集合中,如果所有哈希值都相等。将计算的哈希与集合一起存储。

答案 1 :(得分:2)

这是一个可能的比较器,用于比较IEnumerable<T>的元素。您仍需要在添加之前手动排序。

可以在比较器中构建排序,但我不认为这是明智的选择。添加列表的规范形式似乎更明智。

此代码仅适用于.net 4,因为它利用了泛型差异。如果您需要早期版本,则需要将IEnumerable替换为List,或者为集合类型添加第二个通用参数。

class SequenceComparer<T>:IEqualityComparer<IEnumerable<T>>
{
    public bool Equals(IEnumerable<T> seq1,IEnumerable<T> seq2)
    {
        return seq1.SequenceEqual(seq2);
    }

    public int GetHashCode(IEnumerable<T> seq)
    {
        int hash=1234567;
        foreach(T elem in seq)
            hash=hash*37+elem.GetHashCode();
        return hash;
    }
}

void Main()
{
    var hashSet = new HashSet<List<int>>(new SequenceComparer<int>());

    List<int> test=new int[]{1,3,2}.ToList();
    test.Sort();
    hashSet.Add(test);

    List<int> test2=new int[]{3,2,1}.ToList();
    test2.Sort();       
    hashSet.Contains(test2).Dump();
}

答案 2 :(得分:2)

您是不是只使用数组? int[]会表现得更好。另外我假设列表包含重复项,否则你只是使用集合而没有问题。

一旦它们被添加到HashSet,它们的内容似乎不会改变(很多)。在一天结束时,您将不得不使用一个落在SequenceEqual上的比较器。但是你不必每次都这样做。相反或做一个指数数量的序列比较(例如 - 随着hashset增长,对每个现有成员执行SequenceEqual) - 如果你预先创建一个好的哈希码,你可能不得不做很少这样的比较。虽然生成好的哈希码的开销可能与执行SequenceEqual的开销大致相同,但每个列表只执行一次。

因此,当您第一次对特定List<int>进行操作时,您应该根据有序的数字序列生成一个哈希并对其进行缓存。然后,下次比较列表时,可以使用缓存的值。我不知道你怎么能用我的头顶比较器(也许是一个静态字典?)来实现这一点 - 但是你可以实现List包装器来轻松实现这一点。

这是一个基本想法。您需要小心确保它不易碎(例如,确保在成员更改时使任何缓存的哈希代码无效)但看起来这似乎不是您使用方式的典型情况此

public class FasterComparingList<T>: IList<T>, IList, ... 
    /// whatever you need to implement
{
   // Implement your interfaces against InnerList
   // Any methods that change members of the list need to
   // set _LongHash=null to force it to be regenerated
   public List<T> InnerList { ... lazy load a List }
   public int GetHashCode()
   {
       if (_LongHash==null) {
           _LongHash=GetLongHash();
       }
       return (int)_LongHash;
   }
   private int? _LongHash=null;
   public bool Equals(FasterComparingList<T> list)
   {
       if (InnerList.Count==list.Count) {
           return true;
       }
       // you could also cache the sorted state and skip this if a list hasn't
       // changed since the last sort
       // not sure if native `List` does
       list.Sort();
       InnerList.Sort();
       return InnerList.SequenceEqual(list);
   }
   protected int GetLongHash()
   {
       return .....
       // something to create a reasonably good hash code -- which depends on the 
       // data. Adding all the numbers is probably fine, even if it fails a couple 
       // percent of the time you're still orders of magnitude ahead of sequence
       // compare each time
   } 
}

如果列表一旦添加就不会改变,这应该非常快。即使在列表可能经常更改的情况下,创建新哈希码的时间也不可能与进行序列比较的时间差异很大(如果更大)。

答案 3 :(得分:0)

如果你没有指定IEQualityComparer,那么将使用默认类型,所以我认为你需要做的是创建你自己的IEQualityComparer实现,并将它传递给你的HashSet的构造函数。 Here is a good example

答案 4 :(得分:0)

比较列表的哈希集时,您始终拥有的一个选择是,不对每个元素进行比较,而是对列表进行排序,并使用逗号将它们加入并比较生成的字符串。

因此,在这种情况下,当您创建自定义比较器而不是遍历元素并计算自定义哈希函数时,可以应用此逻辑。