簡體   English   中英

如何創建一個哈希集<List<Int> &gt; 有不同的元素?

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

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM