簡體   English   中英

為什么我不能在沒有枚舉的情況下從HashSet中檢索項目?

[英]Why can't I retrieve an item from a HashSet without enumeration?

我正在尋找洞察HashSet設計師的頭腦。 據我所知,我的問題適用於Java和C#HashSets,讓我覺得必須有一些很好的理由,盡管我自己也想不到。

在我將項目插入HashSet之后,為什么在沒有枚舉的情況下檢索該項目是不可能的,幾乎不是有效的操作? 特別是因為HashSet以支持有效檢索的方式顯式構建。

使用Remove(x)和Contains(x)返回正在刪除或包含的實際項目通常很有用。 這不一定是我傳遞給Remove(x)或Contains(x)函數的項目。 當然,我想我可以通過HashMap實現同樣的效果但是為什么浪費所有這些空間和努力時應該完全可以用一套呢?

我可以理解,可能存在一些設計問題,即添加此功能將允許使用HashSet,這與其角色或框架中的未來角色不一致,但如果是這樣,那么這些設計問題是什么?

編輯

要回答更多問題,請參閱以下詳細信息:

我使用帶有重寫的hashcode,equals等的不可變引用類型來模擬C#中的值類型。 假設類型具有成員A,B和C.Hashcode,equals等僅依賴於A和B.給定A和BI希望能夠從散列集中檢索該等效項並得到它C.我贏了它似乎可以使用HashSet,但我至少想知道這是否有任何充分的理由。 偽代碼如下:

public sealed class X{
 object A;
 object B;
 object extra;

 public int HashCode(){
  return A.hashCode() + B.hashCode();
 }

 public bool Equals(X obj){
  return obj.A == A && obj.B == B;
 }
}

hashset.insert(new X(1,2, extra1));
hashset.contains(new X(1,2)); //returns true, but I can't retrieve extra

在.Net中,您可能正在尋找的是KeyedCollection http://msdn.microsoft.com/en-us/library/ms132438.aspx

你可以通過一些“通用”的聰明來解決每次重新實現這個抽象類的麻煩。 (見IKeyedObject`1。)

注意:任何實現IKeyedObject`1的數據傳輸對象都應該有一個重寫的GetHashCode方法,只需返回this.Key.GetHashCode(); 同樣適用於......

我的基類庫通常最終會包含這樣的內容:

public class KeyedCollection<TItem> : System.Collections.ObjectModel.KeyedCollection<TItem, TItem>
    where TItem : class
{
    public KeyedCollection() : base()
    {
    }

    public KeyedCollection(IEqualityComparer<TItem> comparer) : base(comparer)
    {
    }

    protected override TItem GetKeyForItem(TItem item)
    {
        return item;
    }
}

public class KeyedObjectCollection<TKey, TItem> : System.Collections.ObjectModel.KeyedCollection<TKey, TItem>
    where TItem : class, IKeyedObject<TKey>
    where TKey : struct
{
    public KeyedCollection() : base()
    {
    }

    protected override TItem GetKeyForItem(TItem item)
    {
        return item.Key;
    }
}

///<summary>
/// I almost always implement this explicitly so the only
/// classes that have access without some rigmarole
/// are generic collections built to be aware that an object
/// is keyed.
///</summary>
public interface IKeyedObject<TKey>
{
    TKey Key { get; }
}

您是如何建議從哈希集中檢索項目的? 根據定義,集合沒有以任何方式排序,因此,沒有索引用於檢索有問題的對象。

作為概念,集合用於測試包含,即所討論的元素是否在散列數據集中。 如果您希望使用鍵值或索引從數據源中檢索值,我建議您查看MapList

編輯:基於編輯原始問題的附加答案

Soonil,基於您的新信息,看起來您可能有興趣將您的數據實現為Java Enum,類似於:

 public enum SoonilsDataType {
      A, B, C;

      // Just an example of what's possible
      public static SoonilsDataType getCompositeValue(SoonilsDataType item1,
           SoonilsDataType item2) {
           if (item1.equals(A) && 
                     item2.equals(B)) {
                return C;
           }
      }
 }

Enum自動繼承values(),它返回枚舉“set”中所有值的列表,您可以使用它來以與Set相同的方式測試包含。 另外,因為它是一個完整的類,你可以定義新的靜態方法來執行復合邏輯(就像我試圖在示例代碼中提到的那樣)。 關於Enum的唯一事情就是你不能在運行時添加新的實例,這可能不是你想要的(盡管如果set的數據大小不會在運行時增長,那么Enum就是你想要的)。

如果在插入對象后更改它,則它的散列可能已更改(如果已覆蓋hashCode(),則特別有可能)。 如果哈希值發生更改,則在集合中查找它將失敗,因為您將嘗試查找在與存儲位置不同的位置進行哈希處理的對象。

此外,如果要查找不同實例的相等對象,則需要確保在對象中覆蓋了hashCode和equals。

請注意,這完全適用於Java - 我假設C#有類似的東西,但是自從我使用C#以來已有好幾年了,我會讓別人說出它的功能。

為什么不使用HashMap<X,X> 這完全符合你的要求。 只需每次執行.put(x,x) ,然后你就可以使用.get(x)得到存儲的元素等於.get(x)

我想Set接口和HashSet類的設計者想要確保Collection接口上定義的remove(Object)方法也適用於Set ; 此方法返回一個布爾值,表示對象是否已成功刪除。 如果設計者想要提供刪除(Object)返回Set已經存在的“相等”對象的功能,則這將意味着不同的方法簽名。

另外,假設被刪除的對象在邏輯上等於傳遞給remove(Object)的對象,那么返回包含的對象時添加的值是有爭議的。 但是,我之前遇到過這個問題,並使用Map來解決問題。

請注意,在Java中, HashSet使用HashMap內部,因此沒有在使用額外的存儲開銷HashMap代替。

這是圖書館設計師的疏忽。 正如我在另一個答案中提到的,此方法已添加到.NET Framework 4.7.2 (以及之前的.NET Core 2.0 )中; 請參閱HashSet<T>.TryGetValue 引用來源

/// <summary>
/// Searches the set for a given value and returns the equal value it finds, if any.
/// </summary>
/// <param name="equalValue">The value to search for.
/// </param>
/// <param name="actualValue">
/// The value from the set that the search found, or the default value
/// of <typeparamref name="T"/> when the search yielded no match.</param>
/// <returns>A value indicating whether the search was successful.</returns>
/// <remarks>
/// This can be useful when you want to reuse a previously stored reference instead of 
/// a newly constructed one (so that more sharing of references can occur) or to look up
/// a value that has more complete data than the value you currently have, although their
/// comparer functions indicate they are equal.
/// </remarks>
public bool TryGetValue(T equalValue, out T actualValue)

在我看來,你實際上正在尋找一個Map<X,Y> ,其中Y是extra1的類型。


(下面咆哮)

equals和hashCode方法定義有意義的對象相等性。 HashSet類假定如果Object.equals(Object)定義的兩個對象相等,則這兩個對象之間沒有區別。

我甚至可以說,如果object extraobject extra是有意義的,那么你的設計並不理想。

解決了 希望找到一個元素對我來說似乎完全有效,因為用於搜索的代表可能與找到的元素不同。 如果元素包含鍵和值信息,並且自定義相等比較器僅比較關鍵部分,則尤其如此。 請參閱代碼示例。 該代碼包含一個比較器,它實現自定義搜索捕獲找到的元素。 這需要比較器的一個實例。 清除對找到的元素的引用。 通過Contains執行搜索。 訪問找到的元素。 共享比較器實例時請注意多線程問題。

using System;
using System.Collections.Generic;

namespace ConsoleApplication1 {

class Box
{
    public int Id;
    public string Name;
    public Box(int id, string name)
    {
        Id = id;
        Name = name;
    }
}

class BoxEq: IEqualityComparer<Box>
{
    public Box Element;

    public bool Equals(Box element, Box representative)
    {
        bool found = element.Id == representative.Id;
        if (found)
        {
            Element = element;
        }
        return found;
    }

    public int GetHashCode(Box box)
    {
        return box.Id.GetHashCode();
    }
}

class Program
{
    static void Main()
    {
        var boxEq = new BoxEq();
        var hashSet = new HashSet<Box>(boxEq);
        hashSet.Add(new Box(3, "Element 3"));
        var box5 = new Box(5, "Element 5");
        hashSet.Add(box5);
        var representative = new Box(5, "Representative 5");
        boxEq.Element = null;
        Console.WriteLine("Contains {0}: {1}", representative.Id, hashSet.Contains(representative));
        Console.WriteLine("Found id: {0}, name: {1}", boxEq.Element.Id, boxEq.Element.Name);
        Console.WriteLine("Press enter");
        Console.ReadLine();
    }
}

} // namespace

這些語言中的集合對象大多設計為值集,而不是可變對象。 他們通過使用equals來檢查放入它們的對象是否是唯一的。 這就是為什么contains和remove返回boolean而不是對象:它們檢查或刪除傳遞給它們的值。

實際上,如果你在一個集合上做一個包含(X),並期望得到一個不同的對象Y,那就意味着X和Y是等於(即X.equals(Y)=> true),但有些不同,似乎錯了。

通過讓我自己的對象將自己定義為KeyValuePairs,我得到了一個關於使用Map的方法的有趣建議。 雖然是一個很好的概念,但遺憾的是KeyValuePair不是一個界面(為什么不呢?)並且是一個結構,它可以在空中拍攝這個計划。 最后我將滾動我自己的Set,因為我的約束允許我這個選項。

想知道同樣的事情,並且能夠很好地查看源代碼:

來源: http//referencesource.microsoft.com/#System.Core/System/Collections/Generic/HashSet.cs

集合是唯一項(對象或值)的集合。 在.net實現中,如果比較器的Equals方法對這兩個項返回true,則項與另一個項(非唯一)相同。 如果這兩個項具有相同的哈希碼,則不會。 所以檢查項目是否存在是一個兩步過程。 首先使用hashset來最小化要主持的項目數,然后是壓縮本身。

如果要檢索項目,則必須能夠為檢索功能提供唯一標識符。 您可能知道所需項目的哈希碼。 但這還不夠。 因為多個項目可以具有相同的哈希值。 您還需要提供項目本身,以便可以調用Equal方法。 如果你有這個項目就沒有理由得到它。

可以創建一個數據結構,要求沒有兩個唯一的項返回相同的哈希碼。 而且你可以從它得到一個項目。 添加*會更快,如果你知道哈希就可以檢索。 如果兩個不相等但返回相同散列的項目被放入其中,則第一個將被覆蓋。 據我所知,這個類型在.net中不存在,並且這與字典不同。

*鑒於GetHash方法是相同的。

簡短的回答; 因為物品不能保證是不可變的。

我已經遇到了您描述的確切問題,其中HashCode基於成員類中的固定字段,但該類包含可以在不更改哈希值的情況下更新的其他信息。

我的解決方案是基於ICollection <T>實現一個通用的MyHashSet <T>,但是繞過Dictionary <int,List <T >>以提供所需的查找效率,其中int鍵是T的HashCode。但是,這個表明如果成員對象的HashCode可以更改,那么字典查找后跟列表中項目的相等比較將永遠不會找到更改的項目。 沒有強制成員不可變的機制,因此唯一的解決方案是枚舉該批次。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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