13

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

为了保持唯一性,我目前必须做两件事:1. 手动循环现有列表,使用SequenceEquals. 2. 对单个列表进行排序,以便SequenceEquals当前有效。

有一个更好的方法吗?是否有一个现有的 IEqualityComparer 可以提供给 HashSet 以便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);
}
4

5 回答 5

7

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

于 2011-04-01T20:26:43.117 回答
6

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

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

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

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 = unchecked(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();
}
于 2011-04-01T20:29:58.980 回答
2

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

一旦将它们添加到HashSet. 归根结底,您将不得不使用依赖于SequenceEqual. 但是您不必每次都这样做。相反,或者进行指数级的序列比较(例如——随着哈希集的增长,SequenceEqual对每个现有成员进行一次)——如果你预先创建了一个好的哈希码,你可能需要做很少的这样的比较。虽然生成良好哈希码的开销可能与执行 a 的开销大致相同,但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
   } 
}

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

于 2011-04-01T20:33:50.317 回答
0

如果您不指定 IEQualityComparer,则将使用默认类型,因此我认为您需要创建自己的 IEQualityComparer 实现,并将其传递给 HashSet 的构造函数。这是一个很好的例子

于 2011-04-01T19:38:26.163 回答
0

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

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

于 2019-10-30T20:58:29.277 回答