为什么我不能在没有枚举的情况下从HashSet中检索项目?

时间:2009-09-29 20:41:05

标签: c# java hashset

我正在寻找洞察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

12 个答案:

答案 0 :(得分:10)

在.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; }
}

答案 1 :(得分:9)

您是如何建议从哈希集中检索项目的?根据定义,集合没有以任何方式排序,因此,没有索引可用于检索有问题的对象。

作为一个概念,设置用于测试包含,即所讨论的元素是否在散列数据集中。如果您希望使用键值或索引从数据源中检索值,我建议您查看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就是你想要的)。 / p>

答案 2 :(得分:4)

如果在插入对象后更改了对象,则它的散列可能已更改(如果已覆盖hashCode(),则特别有可能)。如果哈希值发生更改,则在集合中查找它将失败,因为您将尝试查找在与存储位置不同的位置进行哈希处理的对象。

此外,如果要查找不同实例的相等对象,则需要确保在对象中覆盖了hashCode和equals。

请注意,这完全是针对Java的 - 我假设C#有类似的东西,但是自从我使用C#已有好几年了,我会让别人说出它的功能。

答案 3 :(得分:3)

我想Set接口和HashSet类的设计者希望确保remove(Object)接口上定义的Collection方法也适用于Set };此方法返回一个布尔值,表示对象是否已成功删除。如果设计者想要提供删除(Object)返回Set中已经存在的“相等”对象的功能,则这将意味着不同的方法签名。

另外,假设被删除的对象在逻辑上等于传递给remove(Object)的对象,那么返回包含的对象时添加的值是有争议的。但是,我之前遇到过这个问题,并使用Map来解决问题。

请注意,在Java中,HashSet在内部使用HashMap,因此使用HashMap时不会产生额外的存储开销。

答案 4 :(得分:3)

为什么不使用HashMap<X,X>?这完全符合你的要求。每次只做.put(x,x),然后你可以使用.get(x)将存储的元素等于x。

答案 5 :(得分:2)

这是图书馆设计师的疏忽。正如我在another answer下所提到的,此方法已添加到.NET Framework 4.7.2(和之前的.NET Core 2.0)中;参见HashSet<T>.TryGetValue。引用the source

/// <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)

答案 6 :(得分:1)

在我看来,您实际上正在寻找Map<X,Y>,其中Y是extra1的类型。


(下面咆哮)

equals和hashCode方法定义有意义的对象相等性。 HashSet类假定如果两个对象与Object.equals(Object)定义的相等,则这两个对象之间没有区别。

我甚至可以说如果object extra有意义,那么你的设计并不理想。

答案 7 :(得分:1)

<强>解决即可。希望找到一个元素对我来说似乎完全有效,因为用于搜索的代表可能与找到的元素不同。如果元素包含键和值信息,并且自定义相等比较器仅比较关键部分,则尤其如此。请参阅代码示例。该代码包含一个比较器,用于实现捕获找到的元素的自定义搜索。这需要比较器的一个实例。清除对找到的元素的引用。通过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

答案 8 :(得分:0)

这些语言中的设置对象大多设计为值集,而不是可变对象。他们通过使用equals来检查放入它们的对象是否是唯一的。这就是为什么contains和remove返回boolean而不是对象:它们检查或删除传递给它们的值。

实际上,如果你在一个集合上做一个包含(X),并期望获得一个不同的对象Y,那就意味着X和Y是等于(即X.equals(Y)=&gt; true),但是有点不同,这似乎是错误的。

答案 9 :(得分:0)

通过让我自己的对象将自己定义为KeyValuePairs,我获得了一个关于使用Map的方法的有趣建议。虽然是一个很好的概念,但遗憾的是KeyValuePair不是一个界面(为什么不呢?)并且是一个结构,它可以在空中拍摄这个计划。最后,我将滚动我自己的Set,因为我的约束允许我这个选项。

答案 10 :(得分:0)

简短回答;因为这些物品不能保证是不可变的。

我遇到了您描述的确切问题,其中HashCode基于成员类中的固定字段,但该类包含可在不更改哈希值的情况下更新的其他信息。

我的解决方案是实现通用的MyHashSet&lt; T&gt;基于ICollection&lt; T&gt;但是围绕一个字典&lt; int,List&lt; T&gt;&gt;提供所需的查找效率,其中int键是T的HashCode。但是,这表明如果成员对象的HashCode可以更改,那么字典查找后跟列表中项目的相等比较将永远不会找到更改的项目。没有强制成员不可变的机制,因此唯一的解决方案就是枚举该批次。

答案 11 :(得分:0)

在想知道同样的事情后,能够很好地查看源代码:

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

集合是唯一项目(对象或值)的集合。在.net实现中,如果比较器的Equals方法对这两个项返回true,则项与另一个项(非唯一)相同。如果这两个项具有相同的哈希码,则不会。所以检查项目是否存在是一个两步过程。首先使用hashset来最小化要主持的项目数,然后是压缩本身。

如果要检索项目,则必须能够为检索功能提供唯一标识符。您可能知道所需项目的哈希码。但这还不够。因为多个项目可以具有相同的哈希值。您还需要提供项目本身,以便可以调用Equal方法。并且如果你有这个项目就没有理由得到它。

可以创建一个数据结构,要求没有两个唯一的项返回相同的哈希码。而且你可以从它得到一个项目。添加*会更快,如果你知道哈希就可以检索。如果两个不相等但返回相同散列的项目被放入其中,则第一个将被覆盖。据我所知,这种类型在.net中不存在,并且这与字典不相同。

*假设GetHash方法是相同的。