代码之家  ›  专栏  ›  技术社区  ›  sooniln

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

  •  32
  • sooniln  · 技术社区  · 17 年前

    我正在寻找对HashSet设计者头脑的洞察。据我所知,我的问题同时适用于Java和C#hashset,这让我觉得肯定有一些很好的理由,尽管我自己想不出任何理由。

    在我将一个项插入到哈希集中之后,为什么在没有枚举的情况下检索该项是不可能的,这几乎不是一个有效的操作?特别是因为哈希集是以支持高效检索的方式显式构建的。

    让Remove(x)和Contains(x)返回被删除或包含的实际项目对我来说通常是有用的。这不一定是我传递到Remove(x)或Contains(x)函数中的项。当然,我想我可以通过HashMap实现同样的效果,但是为什么要浪费所有的空间和精力呢?如果用一个集合就完全可以做到这一点的话?

    我可以理解,可能存在一些设计问题,即添加此功能将允许使用与其在框架中的角色或未来角色不一致的哈希集,但如果是这样,这些设计问题是什么?

    编辑

    要回答更多问题,请参阅以下详细信息:

    我使用一个不可变的引用类型和重写的hashcode、equals等来模拟C#中的值类型。假设该类型有成员A、B和C。Hashcode、equals等仅依赖于A和B。给定一些A和B,我希望能够从hashset中检索等价项并得到它的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 回复  |  直到 17 年前
        1
  •  11
  •   leat    15 年前

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

    您可以通过一些“通用”的技巧来避免每次重新实现这个抽象类的麻烦。(见Ikeydobject`1。)

    注意:实现IKeyDobject`1的任何数据传输对象都应该有一个重写的GetHashCode方法,只返回这个.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; }
    }
    
        2
  •  9
  •   Peter    17 年前

    您打算如何从哈希集中检索该项?根据定义,集合不以任何方式排序,因此,没有用于检索相关对象的索引。

    Map List

    编辑:基于对原始问题的编辑的附加答案

    Soonil,根据您的新信息,您可能有兴趣将数据实现为Java枚举,类似于:

     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的automatically inherit values()返回Enum“集合”中所有值的列表,您可以使用该列表以与集合相同的方式对其进行包含性测试。另外,因为它是一个完整的类,所以可以定义新的静态方法来执行复合逻辑(就像我在示例代码中试图提及的那样)。关于枚举的唯一一点是,您不能在运行时添加新实例,这可能不是您想要的(尽管如果集合的数据大小在运行时不会增长,那么枚举就是您想要的)。

        3
  •  4
  •   aperkins    17 年前

    如果在插入对象后对其进行更改,则其哈希值可能已更改(如果已重写hashCode(),则这种情况尤其可能发生)。如果散列值发生更改,则在集合中查找它将失败,因为您将尝试查找散列在与其存储位置不同的位置的对象。

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

        4
  •  3
  •   Adamski    17 年前

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

    此外,如果要删除的对象在逻辑上等于传递给remove(对象)的对象,那么在返回包含的对象时添加的值是有争议的。然而,我以前也遇到过这个问题,并且用地图解决了这个问题。

    哈希集 使用 HashMap 哈希图 相反

        5
  •  3
  •   user1111929    14 年前

    为什么不直接使用 HashMap<X,X> ? 这正是你想要的。照办 .put(x,x) 每次,然后你就可以用 .get(x) .

        6
  •  3
  •   Douglas    8 年前

    这是图书馆设计师的疏忽。正如我在下面提到的 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)
    
        7
  •  1
  •   Robert Munteanu    17 年前

    在我看来你真的在找一个 Map<X,Y> extra1 .


    (下文)

    Object.equals(Object) 这两个对象之间没有区别。

    object extra 是有意义的,你的设计并不理想。

        8
  •  1
  •   Michiel de Wolde    10 年前

    . 希望找到一个元素对我来说似乎完全有效,因为用于搜索的代表可能与找到的元素不同。如果元素包含键和值信息,并且自定义相等比较器仅比较键部分,则尤其如此。请参见代码示例。该代码包含一个实现自定义搜索的比较器 和

    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
    
        9
  •  0
  •   penpen    17 年前

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

    实际上,如果你在一个集合上做一个包含(X),并期望得到一个不同的对象Y,这意味着X和Y是相等的(即X.equals(Y)=>true),但有些不同,这似乎是错误的。

        10
  •  0
  •   sooniln    17 年前

    有人给了我一个有趣的建议,让我自己的对象将自己定义为KeyValuePairs,以此来使用映射。虽然这是一个很好的概念,但不幸的是KeyValuePair不是一个接口(为什么不?),而是一个结构,它将该计划抛诸脑后。最后,我将滚动我自己的集合,因为我的约束允许我使用此选项。

        11
  •  0
  •   Richard Petheram    14 年前

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

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

    我的解决方案是实现一个通用的MyHashSet<T>基于ICollection<T>但却裹着一本字典<int,List<T>&燃气轮机;为了提供所需的查找效率,其中int键是T的HashCode。但是,这表明如果成员对象的HashCode可以更改,那么字典查找之后对列表中的项进行相等比较将永远找不到更改的项。没有强制成员不可变的机制,因此唯一的解决方案是枚举批次。

        12
  •  0
  •   user1852503    11 年前

    在对同样的事情感到疑惑,并且能够仔细查看源代码之后:

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

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

    可以创建一个数据结构,要求没有两个唯一的项返回相同的哈希代码。然后你就可以从中得到一件物品。添加*会更快,如果您知道哈希,则可以进行检索。如果将两个不相等但返回相同哈希的项放入其中,则第一个项将被覆盖。据我所知,这种类型在.net中不存在,不,这和字典不一样。