12

Is there a way to remove an entry from a Dictionary (by Key) AND retrieve its Value in the same step?

For example, I'm calling

Dictionary.Remove(Key);

but I also want it to return the Value at the same time. The function only returns a bool.

I know I can do something like

Value = Dictionary[Key];
Dictionary.Remove(Key);

but it seems like this will search the dictionary twice (once to get the value, and another time to remove it from the dictionary). How can I (if possible) do both WITHOUT searching the dictionary twice?

Pang
  • 9,564
  • 146
  • 81
  • 122
Mash
  • 1,496
  • 13
  • 17
  • 8
    possible duplicate of http://stackoverflow.com/questions/15785091/is-there-any-implementation-to-remove-by-key-and-get-the-value-at-the-same-time This thread has an accepted answer – Kenneth Apr 15 '13 at 23:21
  • If it is also performance related, you could thake a look at the answer here http://stackoverflow.com/questions/1869452/a-faster-replacement-to-the-dictionarytkey-tvalue and see if that clears anything up – Silvermind Apr 15 '13 at 23:23
  • @Kenneth: So, I guess it cannot be done with the "built-in" dictionary type in .NET? – Mash Apr 15 '13 at 23:35
  • 2
    @Mash: Assuming your `Key` caches or trivial calculates the Hash code, the performance cost of double lookup is trivial. Most dictionaries have O(1) lookup and removal. – Guvante Apr 15 '13 at 23:39
  • 1
    afaik, no, but since it's such an obvious and a legit requirement, I figured someone must have asked it before. I guess the only possible way would be to implement your own dictionary. Not to be critical, but I doubt that it will be faster :) – Kenneth Apr 15 '13 at 23:52

6 Answers6

9

Starting with .NET Core 2.0, we have:

public bool Remove (TKey key, out TValue value);

https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2.remove?view=netcore-2.0#System_Collections_Generic_Dictionary_2_Remove__0__1__

Note this API hasn't been included in .NET Standard 2.0 and .NET Framework 4.7.

vulcan raven
  • 32,612
  • 11
  • 57
  • 93
2

Because they both have the desired missing method I tried Microsoft's ConcurrentDictionary and C5 from University of Copenhagen http://www.itu.dk/research/c5/ and I can tell with, at least with my use case it was super slow (I mean 5x - 10x slower) compared to Dictionary. I think C5 is sorting both keys and values all the time and Concurrent Dictionary is "too worried" about the calling thread.. I am not here to discuss why those two incarnations of Dictionary are slow. My algorithm was seeking and replacing some entries whereas the first keys would be removed and new keys would be added (some sort of Queue)... The only think left to do was to modify original .Net mscorelib's Dictionary. I downloaded the source code from Microsoft and included the Dictionary class in my source code. To compile I also need to drag along just the HashHelpers class and ThrowHelper class. All that was left was to comment out some lines (e.g. [DebuggerTypeProxy(typeof(Mscorlib_DictionaryDebugView<,>))] and some resource fetching). Obviously I had to add the missing method to the copied class. Also do not try to compile Microsoft Source code you will be doing that for hours, I was lucky enough to get it going.

   public bool Remove(TKey key, out TValue value)
    {
        if (key == null)
        {
            ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
        }

        if (buckets != null)
        {
            int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
            int bucket = hashCode % buckets.Length;
            int last = -1;
            for (int i = buckets[bucket]; i >= 0; last = i, i = entries[i].next)
            {
                if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key))
                {
                    if (last < 0)
                    {
                        buckets[bucket] = entries[i].next;
                    }
                    else
                    {
                        entries[last].next = entries[i].next;
                    }
                    entries[i].hashCode = -1;
                    entries[i].next = freeList;
                    entries[i].key = default(TKey);
                    value = entries[i].value;
                    entries[i].value = default(TValue);
                    freeList = i;
                    freeCount++;
                    version++;
                    return true;
                }
            }
        }
        value = default(TValue);
        return false;
    }

Lastly I modified the namespace to System.Collection.Generic.My In my algorithm I only had two lines where I was getting the value than remove it in the next line.. replaced that with the new method and obtained a steady performance gain of 7%-10%. Hope it helps this use case and any other cases where re-implementing Dictionary from scratch is just not what one should do.

Dan M
  • 770
  • 1
  • 9
  • 18
  • So basically, modify Microsoft's library. This question has been out for a while and this answer appears to be as good as it gets, so I'll accept this solution. Thank you! – Mash Dec 11 '15 at 18:03
1

Even though this is not what the OP has asked for, I could not help myself but post a corrected extension method:

public static bool Remove<TKey, TValue>(this Dictionary<TKey, TValue> self, TKey key, out TValue target)
{
    self.TryGetValue(key, out target);
    return self.Remove(key);
}
bavaza
  • 10,319
  • 10
  • 64
  • 103
1

The concurrentDictionary has a TryRemove method that attempts to remove and return the value that has the specified key from the System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue>. It returns the default value of the TValue type if key does not exist.

https://msdn.microsoft.com/en-us/library/dd287129(v=vs.110).aspx

Prasanth Louis
  • 4,658
  • 2
  • 34
  • 47
0

You can do it with an Extension method:

public static string GetValueAndRemove<TKey, TValue>(this Dictionary<int, string> dict, int key)
{
    string val = dict[key];
    dict.Remove(key);
    return val;    
}

static void Main(string[] args)
{
    Dictionary<int, string> a = new Dictionary<int, string>();
    a.Add(1, "sdfg");
    a.Add(2, "sdsdfgadfhfg");
    string value = a.GetValueAndRemove<int, string>(1);
}
Jeremy Thompson
  • 61,933
  • 36
  • 195
  • 321
  • Since Remove is safe, I would suggest checking if the key exists first, but good idea anyhow :) – Silvermind Apr 15 '13 at 23:32
  • 1
    Thank you for the response, but I should have been more clear when I said "in the same step." What I really mean is not search the dictionary twice. The `string val = dict[key]` will search the dictionary once and the `dict.Remove(key)` will search it again. – Mash Apr 15 '13 at 23:33
  • @Mash Dictionary is really fast, so performance-wise it shouldn't be a big problem. – Silvermind Apr 15 '13 at 23:34
  • 1
    @Silvermind I agree, it really wasn't an issue of performance, just a thought that occurred to me while I was coding something – Mash Apr 15 '13 at 23:36
  • If you look under the hood of the Dictionary in the University of Copenehagen's Generic Collection Library, you'll probably find the same implementation. Unfortunately you cant do this in one step. – Jeremy Thompson Apr 15 '13 at 23:50
  • @JeremyThompson I haven't looked into their code but I'm not willing to go through all the trouble to use their Dictionary over Microsoft's. Upvote for providing something where I can do it in one call. – Mash Apr 16 '13 at 00:27
-1

You can extend the class to add that functionality:

public class PoppableDictionary<T, V> : Dictionary<T, V>
{
    public V Pop(T key)
    {
        V value = this[key];
        this.Remove(key);
        return value;
    }
}
mmutilva
  • 18,688
  • 22
  • 59
  • 82
  • 1
    This still needs two lookups. The OP requested a method to eliminate the double lookup – Kenneth Apr 15 '13 at 23:53
  • You are right. I answered based on @Guvante's comment which states that access operations to dictionaries are o(1) so there's no significant performance penalty in accessing the dictionary twice. – mmutilva Apr 16 '13 at 00:00
  • 1
    @Toto Yes, I was mainly looking for avoiding the double lookup in the dictionary but thank you for providing something where I can at least do it in one call (upvote). – Mash Apr 16 '13 at 00:29