[英]How to create a HashSet<List<Int>> with distinct elements?
我有一个包含多个整数列表的 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);
}
这开始是错误的,它必须是HashSet<ReadOnlyCollection<>>
因为您不能允许列表更改并使集合谓词无效。 然后,当您将集合添加到集合时,这允许您在 O(n) 中计算哈希码。 如果所有散列结果都相等,则进行 O(n) 测试以检查它是否已经在一个非常罕见的 O(n^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 = 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();
}
您是否有理由不只是使用数组? int[]
会表现得更好。 另外我假设列表包含重复项,否则您只会使用集合而没有问题。
一旦它们被添加到HashSet
中,它们的内容似乎不会改变(很多)。 归根结底,您将不得不使用依赖于SequenceEqual
的比较器。 但是您不必每次都这样做。 相反,或者进行指数级的序列比较(例如——随着哈希集的增长,对每个现有成员执行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
}
}
如果列表一旦添加就不会改变,这应该非常快。 即使在列表可能经常更改的情况下,创建新哈希码的时间也可能与进行序列比较的时间差别不大(如果甚至更大)。
如果您不指定 IEQualityComparer,则将使用默认类型,因此我认为您需要创建自己的 IEQualityComparer 实现,并将其传递给 HashSet 的构造函数。 这是一个很好的例子。
在比较列表的哈希集时,您始终拥有的一个选项是,不是比较每个元素,而是对列表进行排序并使用逗号连接它们并比较生成的字符串。
因此,在这种情况下,当您创建自定义比较器而不是迭代元素并计算自定义哈希函数时,您可以应用此逻辑。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.