2012-04-13 23 views
3

我已經寫了一個方法,它需要能夠接受任意數量的數據字段,將它們以某種方式組合到一個可哈希對象,然後將該對象散列在字典中供以後查找。爲任意一組密鑰(任意數據類型)獲取可排列對象的最有效方法

到目前爲止,我想到的最好的算法是爲每個字段帶上ToHashCode(),然後使用某種分隔符(例如「|」)將得到的哈希碼連接到一個字符串中,然後使用這個結果字符串作爲字典的唯一鍵。

有誰知道更有效的方法來做到這一點?我在想,也許有一些方法可以獲取每個字段的哈希碼,並進行一些數學運算,將它們組合成一個獨特的可哈希數字,但這只是一個猜測。

感謝您的任何幫助。

編輯: 我認爲人們可能會對我的意思感到困惑。在這種情況下元組將不起作用,因爲我需要一個任意字段數組合成一個可哈希對象。只有在運行時才知道字段的數量,而不是在設計時。

另一個關於將所有哈希碼數學地組合成新的哈希碼的解決方案也不起作用,因爲我需要一個對象,它可以用作字典中的關鍵字。我相信,使用散列碼作爲字典中的關鍵字非常危險。

編輯2: 考慮到這一點後,我認爲我的原始解決方案不是一個好的解決方案。在有單個字段的極限情況下,我的解決方案退化爲將字符串版本的哈希碼放入Dictionary中。

我想或許一個更好的解決方案是創建一個新的類型,它在構造函數中使用一個枚舉類型,並實現GetHashCode()。 GetHashCode()函數然後循環遍歷枚舉的每個值,並在散列碼函數中執行通常類型的累加器邏輯。這樣,該對象可以被粘貼到字典,哈希集等中,並按照您的預期行事。

+0

無論你選擇什麼,你都會冒着碰撞的危險。對於你的字符串版本,它可能可以忽略不計。 – 2012-04-13 19:14:32

+0

是的,你永遠無法完全避免碰撞,因爲哈希碼只有有限數量的不同可能值。 – 2012-04-13 19:23:33

+0

我會投票使用'GetHashCode()'的數學組合並將其用作關鍵字,並使其能夠很好地處理碰撞。例如'Dictionary >',如果列表包含多個對象,則比較它們以找到正確的對象。 – Thymine 2012-04-13 20:55:47

回答

1

這裏的關鍵是意識到任何任意大小的對象集合都可以通過簡單地將其視爲IEnumerable來散列,其哈希碼取決於枚舉的內容。

爲此,我簡單地創建了一個實現IEnumerable的ValueAwareEnumerable類。這個類在其唯一的構造函數中使用一個枚舉。然後它重寫GetHashCode()和Equals(),以便它們依賴於可枚舉的內容。 GetHashCode方法很簡單:

public override int GetHashCode() 
{ 
    unchecked 
    { 
     int hash = 983; 
     foreach (var item in _wrappedEnumerable) 
      if(item != null) 
       hash = hash * 457 + item.GetHashCode(); 
     return hash; 
    } 
} 

和equals:

public override bool Equals(object obj) 
{ 
    if (ReferenceEquals(null, obj)) return false; 
    if (ReferenceEquals(this, obj)) return true; 
    if (obj.GetType() != typeof (ValueAwareEnumerable<T>)) return false; 
    return Equals((ValueAwareEnumerable<T>) obj); 
} 

public bool Equals(ValueAwareEnumerable<T> other) 
{ 
    if (ReferenceEquals(null, other)) return false; 
    if (ReferenceEquals(this, other)) return true; 

    return _wrappedEnumerable.SequenceEqual(other);        
} 

的這裏需要注意的是,它取決於枚舉的順序上。如果需要,可以通過在迭代遍歷它之前簡單地使GetHashCode()和Equals()對枚舉進行排序來使其與順序無關。

要完成它,只添加一個擴展方法某處的好措施:

public static IEnumerable<T> ToValueAwareEnumerable<T>(this IEnumerable<T> enumerable) 
{ 
    return new ValueAwareEnumerable<T>(enumerable); 
} 

你可以做這樣的事情:

var dictionary = new Dictionary<IEnumerable<int>>(); 
var veryImportantNumbers = new[] { 5, 8, 13, 20, 3, 100, 55, -5, 0 }; 
dictionary[veryImportantNumbers.ToValueAwareEnumerable()] = "Pastrami"; 

這將任何數據類型工作,即使是混合數據類型,如果您將它們視爲IEnumerable<Object>

+0

+1,不要忘記檢查哈希函數中的空值,它不應該拋出異常。就像'hash = hash * 457 +(item == null?0:item.GetHashCode());' – nawfal 2013-11-09 14:35:58

1

最簡單的方法是使用Tuple <>來組合字段的哈希碼。

var dict = new Dictionary<Tuple<int, string>, MyClass>(); 
dict[Tuple.Create(myObj.Num, myObj.Str)] = myObj; 

你也可以自己組合哈希,但是你冒險弄錯了。

+0

它也鏈接平等。這可能是唯一的選擇。 – 2012-04-13 19:16:41

+0

這是最容易維護的,內置元組也很快。 – 2012-04-13 19:22:34

0

我在想也許有一些方法可以把每個字段的哈希碼,並做一些數學運算,將它們組合成一個獨特的可哈希數字,但這只是一個猜測。

是的,這正是你應該做的。這裏有一個常見的實現:

unchecked 
{ 
    int hash = 983; 
    hash = hash * 457 + x.GetHashCode(); 
    hash = hash * 457 + y.GetHashCode(); 
    hash = hash * 457 + (z != null ? z.GetHashCode() : 0); 
    return hash; 
} 

請注意,你不應該使用的哈希碼作爲字典鍵,因爲它不會是唯一的(衝突通常是罕見的,但他們不是不可能)。如果你想使用對象本身爲重點,你還必須重寫Equals所以如果x.Equals(y),然後x.GetHashCode() == y.GetHashCode()(反向並不一定是真實的)

+0

手動組合散列應該比使用Tuple <>運行速度快一些,但是它需要付出代價。除非仔細分析顯示,否則我建議不要手動組合哈希值。 – 2012-04-13 19:20:57

+0

@EldritchConundrum,我想這取決於你想做什麼......我不確定我是否正確理解了OP的要求。此外,Tuple 在.NET 3.5及更早版本中不可用。 – 2012-04-13 19:22:39

+0

是的,這是真的。在元組之前,您必須手動組合哈希值。所以,最好的答案取決於.Net版本和OP所說的「高效率」:)。 – 2012-04-13 19:26:28

0

你不能安全使用的標準有表在這種情況下(除非你可以提供額外的限制)。

需要額外的信息來提供一個很好的選擇,但我有一個建議如下。附加信息可能包括:

  • 使用案例(你如何使用查詢系統,爲什麼你需要的關鍵領域的一部分)
  • 是可以在設計時(注組合中定義的字段:這不是多少個或哪些字段被組合,而是涉及何時/何時/如何定義這些字段以使它們可以組合)。
  • 如果字段是在運行時定義的,那麼總共有多少個字段(所有字段的數量)。
  • 這個奇怪的密鑰存儲了什麼數據?
  • 數據的多久寫入/讀取?

快速解決
使用嵌套的哈希表。對於這種解決方案,您需要對您的字段進行排序。第一個字段是第一個表的關鍵字。這將指向另一個散列表,其中第二個字段將是關鍵。這將發生在每個領域直到你最後一個領域。最後一個字段將是您正在查找的數據的關鍵。
爲了完成這項工作,您需要定義一個自定義對象,該對象具有數據屬性和散列表屬性。

雖然這是一個正確的解決方案,它使用現有的.net數據結構,但效率不高。要獲得更高效的解決方案,請提供更多信息。

相關問題