【问题标题】:Get an equal object from HashSet<T> in O(1)在 O(1) 中从 HashSet<T> 中获取一个相等的对象
【发布时间】:2012-06-06 23:02:03
【问题描述】:

HashSet&lt;T&gt; 可以在 O(1) 中确定它是否包含某个项目。如果我在我的自定义类上覆盖 Equals()GetHashCode(),我可以拥有一个对象 A 和另一个对象 A',它们的身份相等,但 Equals() 返回 trueGetHashCode() 返回相同的哈希码。

现在,假设 A 在散列集中,我想在给定 A' 的 O(1) 中检索 A(从散列集的角度来看,它等于 A)。

var a = new MyClass("A");
var a_prime = new MyClass("A");
Debug.Assert(a.Equals(a_prime));
Debug.Assert(a.GetHashCode() == a_prime.GetHashCode());

var set = new HashSet<MyClass>();
set.Add(a);
Debug.Assert(set.Contains(a_prime));

// This:    
var retrieved_a = set.Get(a_prime);

如何做到这一点?

(请注意,this 没有我正在寻找的答案,this 根本没有答案。)


一些背景信息:我想使用集合来实习生我自己的对象,就像C#实习生字符串一样:相等的对象只需要一个实例。通过这种方式,我可以将元数据附加到这样的对象,并确保在没有该元数据的任何地方都没有其他相同的实例。

【问题讨论】:

标签: c# set


【解决方案1】:

HashSet 上没有任何方法可以满足您的需求。

您可以改用Dictionary

var dict = new Dictionary<MyClass, MyClass>();
dict[a] = a;
Debug.Assert(dict.ContainsKey(a_prime));
var retrieved_a = dict[a_prime];

【讨论】:

  • 我一直避免制作映射到自己的字典。这是不好的做法吗?还是我什么都不担心?
  • @Hans 一如既往,这取决于。为什么你认为这是一个不好的做法?为什么要避开它们?
  • @svick 我不知道,这对我来说似乎很奇怪。我想我没有任何具体的理由来避免它,但我从来没有使用过这样的构造,但出于某种原因,看着 dict[a] = a; 让我畏缩。
  • 这也让我感到畏缩。如果没有别的,它有一个明显的冗余。不过,除非遇到性能问题,否则可能不值得修复。
【解决方案2】:

如果我没记错的话,Dictionary 没有基本集合操作的恒定时间实现,而HashSet 有。这是一种使用恒定时间相等查找来实现它的方法,而不会增加 HashSet 的其他复杂性。如果您需要抓取许多随机元素,则使用此方法至关重要。我下面写的是Java语法,因为我不懂C#,但这个想法是与语言无关的。

class MySet<A> {
     ArrayList<A> contents = new ArrayList();
     HashMap<A,Integer> indices = new HashMap<A,Integer>();

     //selects equal element in constant time
     A equalElement(A input) {
         return contents.get(indices.get(input));
     }

     //adds new element in constant time
     void add(A a) {
         indices.put(a,contents.size());
         contents.add(a);
     }

     //removes element in constant time
     void remove(A a) {
         int index = indices.get(a);
         contents.set(index,contents.get(contents.size()-1));
         contents.remove(contents.size()-1);
         indices.set(contents.get(contents.size()-1),index);
         indices.remove(a);
     }

     //all other operations (contains(), ...) are those from indices.keySet()
}

【讨论】:

  • 对不起,随机是一个错字,我的意思是相等而不是随机。我现在已经更正了这个方法。基本上,我没有使用 HashSet,而是使用 HashMap ArrayList,它们包含相同的元素,并且 HashMap 指向 ArrayList 中该元素的索引。 HashMap 不是简单的来自 HashSet 的“是的,我已经有了这个键”,而是说“是的,我已经有了这个键,它指向整数 i”。然后在 ArrayList 中,您可以通过在位置 i 获取对象来获取您的对象。
  • 感谢指正!现在它更相关了!作为旁注,我仍然会非常小心那个“恒定时间”的说法。我不认为 HashMaps 保证了最坏情况的 O(1) 获取时间,我认为它是平均的 O(1),如果你恶意修改键的 GetHashCode,你会变得更糟的地图。无论如何,删除 -1 和之前的评论,因为它与现在的帖子不匹配。
  • 你说得对,“恒定时间”我的意思是“平均时间:恒定”。
  • 这太棒了,+1。虽然我的性能主义者会将其实现为 HashMap&lt;A, A&gt; 并手动实现集合操作。
  • 我认为的一个小更正:从contents 删除项目后,您不能执行contents.get(contents.size()-1)。它会给你错误的项目。在contents.remove(contents.size()-1);之前应该是indices.set(contents.get(contents.size()-1),index);
【解决方案3】:

使用HashSet.TryGetValue。 (从.NET Framework 4.7.2开始提供。)

【讨论】:

    猜你喜欢
    • 2014-01-02
    • 1970-01-01
    • 1970-01-01
    • 2013-03-21
    • 1970-01-01
    • 2020-10-19
    • 2015-01-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多