[英]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.