简体   繁体   中英

Get original value from HashSet

UPDATE: Starting with .Net 4.7.2, HashSet.TryGetValue - docs is available.
HashSet.TryGetValue - SO post


I have a problem with HashSet because it does not provide any method similar to TryGetValue known from Dictionary . And I need such method -- passing element to find in the set, and set returning element from its collection (when found).

Sidenote -- "why do you need element from the set, you already have that element?". No, I don't, equality and identity are two different things.

HashSet is not sealed but all its fields are private, so deriving from it is pointless. I cannot use Dictionary instead because I need SetEquals method. I was thinking about grabbing a source for HashSet and adding desired method, but the license is not truly open source (I can look, but I cannot distribute/modify). I could use reflection but the arrays in HashSet are not readonly meaning I cannot bind to those fields once per instance lifetime.

And I don't want to use full blown library for just single class.

So far I am stuck with LINQ SingleOrDefault . So the question is how fix this -- have HashSet with TryGetValue ?

Probably you should switch from a HashSet to a SortedSet

There is a simple TryGetValue() for a SortedSet :

public bool TryGetValue(ref T element)
{
    var foundSet = sortedSet.GetViewBetween(element, element);
    if(foundSet.Count == 1)
    {
        element = foundSet.First();
        return true;
    }
    return false;       
}

when called, the element needs just all properties set which are used in the Comparer. It returns the element found in the Set.

I agree this is something which is basically missing. While it's only useful in rare cases, I think they're significant rare cases - most notable, key canonicalization.

I can only think of one suggestion at the moment, and it's truly foul.

You can specify your own IEqualityComparer<T> when creating a HashSet<T> - so create one which remembers the arguments to the last positive (ie true-returning) Equals comparison it has performed. You can then call Contains , and see what the equality comparer was asked to compare.

Caveats:

  • This holds on to references unnecessarily, so could end up preventing objects being garbage collected
  • You'd potentially want to do this on a per-thread basis (if you've got a set that isn't modified after initialization, but is then read by multiple threads, for example)
  • It assumes that HashSet<T> doesn't use any optimization such as "if the references are equal, don't bother consulting the equality comparer"
  • It's fundamentally a horrible abuse

I've been trying to think of other alternatives in terms of finding intersections, but I haven't got anywhere yet...

As noted in comments, it would be worth encapsulating this as far as possible - I suspect you only need a very limited set of operations, so I'd wrap a HashSet<T> in your own class and only expose the operations you really need - that way you get to clear the "cache" after each operation, removing my first objection above.

It still feels like a horrible abuse to me, but...

As others have suggested, an alternative would be to use a Dictionary<TKey, TValue> and implement SetEquals yourself. That would be simple enough to do - and again, you'd want to encapsulate this in your own type. Either way, you should probably design the type itself first, and then implement it using either a HashSet<> or a Dictionary<,> as an implementation detail.

Sounds like you trying to use the wrong tool. True, you can save some memory using a HashSet but it seems to me that you are trying to acheeve a different goal: Get the actual element that is just equal to a representation . So in reality they are two different elements. Just the memento (a unique representation) is equal.

Therefore you'd be better of using a Dictionary where you add your elements as Key and Value . So you're able to get it back (the identical) but you miss your SetEquals ....

I suppose SetEquals in it's implementation does nothing much different than sequencially compare two HashSets in it's bucket order and fails on first non-equality.

So you should be equally good off using a simple SequenceEqual() (LINQ) comparing the two Keys collections.

So this extension method could do

public static SetEqual<T,G>(this IDictionary<T,G> d, IDictionary<T,G> e)
{
    return d.Keys.SequenceEqual(e.Keys);
}

This should work, because a Dictionary basically is a HashSet with an associated value. And more appropriate to your problem. (OK, to be correct, the code should go for Dictionary<> instead of IDictionary<> because Key order matters)

If you need an IEnumerable<> on the second parameter try sorting to get a defined order (not so efficient).

hopefully not blind but I haven't seen this answer anywhere. If you want dictionary's TryGetValue , you can just steal it.

theHashset.ToDictionary(item => item.ID).TryGetValue(key, out value)

All you need is a quick lambda for determining unique keys.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM