2011-06-09 77 views
14

我想知道是否可以就哪種方法更好地創建一組不同的元素達成共識:C# HashSet或使用IEnumerable's .Distinct()這是一個Linq函數?有什麼更好的創建不同的數據結構:HashSet或Linq的Distinct()?

比方說,我通過從數據庫中查詢的結果與DataReader的循環,和我的選擇是增加我構建一個List<SomeObject>或一個HashSet<SomeObject>隨着List選擇的對象,我會風不得不這樣做:

myList = myList.Distinct().ToList<SomeObject>();

隨着HashSet,我的理解是,將元素添加到它本身需要照顧的非重複的,假設你在overrided的SomeObject和GetHashCode()方法Equals()。我主要關心選項的風險和性能方面。

謝謝。

回答

2

「更好」是一個棘手的詞使用 - 它可以對不同的人意味着很多不同的東西。

爲了便於閱讀,我會去Distinct(),因爲我個人覺得這更容易理解。爲了提高性能,我懷疑手工製作的HashSet實現可能會稍微快一點 - 但我懷疑它會非常不同,因爲Distinct的內部實現無疑會使用某種形式的散列。

對於我認爲是「最好」的實現......我認爲你應該使用Distinct,但不知何故將其推向數據庫層 - 即在填充DataReader之前更改底層數據庫SELECT。

1

對於大集合HashSet可能會更快。它依賴於對象的哈希碼來快速確定一個元素是否已經存在於集合中。

在實踐中,它(很有可能)並不重要(但如果你在意你應該測量)。

我本能地猜測HashSet會更快,因爲它使用了快速散列檢查。但是,我查閱了參考資源中當前(4.0)Distinct的實現,並在封面下使用了類似的Set類(它也依賴於散列)。結論;沒有實際的性能差異。

對於您的情況,我會選擇.Distinct以提高可讀性 - 它清楚地表達了代碼的意圖。但是,我同意其他答案之一,如果可能的話,您可能應該在數據庫中執行此操作。

8

還有什麼更好的是描述你的意圖最有表現力的東西。內部實現細節或多或少會相同,區別在於「誰在編寫代碼?「

如果您意向是從地上爬起來的是說,項目的集合源創建項目的獨特收藏,我認爲爲HashSet<T>,你必須創建項目,你必須建立收集,你不妨建立從一開始就正確的。

否則,如果你已經有項目的集合,你想消除重複,我認爲對於調用Distinct()。您已經有一個集合,你只需要一個富有表現力的方式來獲得不同的項目。

+0

+1爲唯一正確的答案! – nawfal 2012-11-22 14:56:10

1

如果你循環讀取DbReader的結果,將你的resutls添加到Hashset會比將它添加到List更好,而不是將它做在Distinct上。你會節省一個迭代。 (獨特的內部使用HashSet)

11

安東尼佩格拉姆說它是最好的。使用正確的工具來完成這項工作。我這樣說是因爲在性能方面DistinctHashSet沒有那麼大的不同。當收藏應該只保留獨特的東西時,請使用HashSet。它也告訴程序員你不能添加重複項。使用正常的List<T>.Distinct()就可以了,以後必須添加重複項並刪除重複項。意圖很重要。

一般來說,

a)如果您要添加的分貝新的對象和你沒有指定自己的自定義Equals HashSet的可能沒有任何好處。來自db的每個對象都可以成爲你的哈希集的一個新實例(如果你只是新的),這將導致集合中的重複。在這種情況下,請使用正常的List<T>。 b)如果確實有一個爲hashset定義的相等比較器,並且你的集合應該只包含不同的對象,則使用hashset。如果你確實有一個爲hashset定義的相等比較器,並且你希望只有來自db的不同對象,但是集合不需要總是隻保存不同的對象(即以後需要添加重複對象),更快的方法是獲得從db到hashset的項目,然後從該hashset返回一個常規列表。

d)你應該做的最好的事情是給數據庫刪除重複的任務,這是正確的工具而且這是頭等艙!

至於性能差異,在我的測試中,我總是發現HashSet更快,但那只是邊緣。 這很明顯考慮到List方法,你必須首先添加,然後做一個獨特的。

測試方法:有兩個一般功能出發,

public static void Benchmark(Action method, int iterations = 10000) 
{ 
    Stopwatch sw = new Stopwatch(); 
    sw.Start(); 
    for (int i = 0; i < iterations; i++) 
     method(); 

    sw.Stop(); 
    MsgBox.ShowDialog(sw.Elapsed.TotalMilliseconds.ToString()); 
} 

public static List<T> Repeat<T>(this ICollection<T> lst, int count) 
{ 
    if (count < 0) 
     throw new ArgumentOutOfRangeException("count"); 

    var ret = Enumerable.Empty<T>(); 

    for (var i = 0; i < count; i++) 
     ret = ret.Concat(lst); 

    return ret.ToList(); 
} 

實現:

var d = Enumerable.Range(1, 100).ToList().Repeat(100); 
HashSet<int> hash = new HashSet<int>(); 

Benchmark(() => 
{ 
    hash.Clear(); 
    foreach (var item in d) 
    { 
     hash.Add(item); 
    } 
}); 

〜3300毫秒

var d = Enumerable.Range(1, 100).ToList().Repeat(100); 
List<int> list = new List<int>(); 

Benchmark(() => 
{ 
    list.Clear(); 
    foreach (var item in d) 
    { 
     list.Add(item); 
    } 

    list = list.Distinct().ToList(); 
}); 

〜5800 ms

當迭代10000次時,2.5秒的差異對於10000個對象的列表並不差。對於正常情況下的差異幾乎不會引人注目。

可能是最好的方法爲你當前的設計:

var d = Enumerable.Range(1, 100).ToList().Repeat(100); 
HashSet<int> hash = new HashSet<int>(); 
List<int> list = new List<int>(); 

Benchmark(() => 
{ 
    hash.Clear(); 
    foreach (var item in d) 
    { 
     hash.Add(item); 
    } 

    list = hash.ToList(); 
}); 

〜3300毫秒

沒有任何顯著差異,看..


部分無關 - 發佈此答案後,我很想知道什麼是最好的方法從正常列表中刪除重複項。

var d = Enumerable.Range(1, 100).ToList().Repeat(100); 
HashSet<int> hash = new HashSet<int>(); 
List<int> list = new List<int>(); 

Benchmark(() => 
{ 
    hash = new HashSet<int>(d); 
}); 

〜3900毫秒

var d = Enumerable.Range(1, 100).ToList().Repeat(100); 
List<int> list = new List<int>(); 

Benchmark(() => 
{ 
    list = d.Distinct().ToList(); 
}); 

〜3200毫秒

這裏正確的工具Distinct比的hackish HashSet更快!也許它是創建散列集的開銷。


我已經測試過各種其他組合如引用類型,沒有在原始列表中重複等結果是一致的。

相關問題