16

I see this question.

How can I get the last element in a SortedDictionary in .Net 3.5.

Community
  • 1
  • 1
leora
  • 188,729
  • 360
  • 878
  • 1,366

5 Answers5

26

Last extension method will give you the result, but it will have to enumerate the entire collection to get you there. It's such a shame SortedDictionary<K, V> doesn't expose Min and Max members especially considering internally it is backed by a SortedSet<KeyValuePair<K, V>> which has Min and Max properties.

If O(n) is not desirable, you have a few options:

  1. Switch to a SortedList<K, V>. Again for some reason BCL doesn't pack this by default. You can use indexers to get max (or min) value in O(1) time. Extending with extension methods will be nice.

    //Ensure you dont call Min Linq extension method.
    public KeyValuePair<K, V> Min<K, V>(this SortedList<K, V> dict)
    {
        return new KeyValuePair<K, V>(dict.Keys[0], dict.Values[0]); //is O(1)
    }
    
    //Ensure you dont call Max Linq extension method.
    public KeyValuePair<K, V> Max<K, V>(this SortedList<K, V> dict)
    {
        var index = dict.Count - 1; //O(1) again
        return new KeyValuePair<K, V>(dict.Keys[index], dict.Values[index]);
    }
    

    SortedList<K, V> comes with other penalties. So you might want to see: What's the difference between SortedList and SortedDictionary?

  2. Write your own SortedDictionary<K, V> class. This is very trivial. Have a SortedSet<KeyValuePair<K, V>> as the internal container and base the comparison on the Key part. Something like:

    public class SortedDictionary<K, V> : IDictionary<K, V>
    {
        SortedSet<KeyValuePair<K, V>> set; //initialize with appropriate comparer
    
        public KeyValuePair<K, V> Min { get { return set.Min; } } //O(log n)
        public KeyValuePair<K, V> Max { get { return set.Max; } } //O(log n)
    }
    

    This is O(log n). Not documented, but I checked the code.

  3. Use fiddly reflection to access the backing set which is private member of SortedDictionary<K, V> class and invoke Min and Max properties. One can rely on expressions to compile a delegate and cache it for performance. It's a very poor choice to do so. Can't believe I suggested this.

  4. Rely on other implementations, for eg. For TreeDictionary<K, V> from C5. They have FindMin and FindMax both of which are O(log n)

Community
  • 1
  • 1
nawfal
  • 70,104
  • 56
  • 326
  • 368
  • Might want to re-order these options so that the better options are at the top, rather than having them in, what I presume, is the order that you thought of them. – Servy Jun 11 '14 at 16:16
  • How would you implement the indexer/`TryGetValue` for your second option? – CodesInChaos Oct 18 '15 at 18:20
  • @CodesInChaos you're right, that makes it useless. It's sad sets in .NET don't expose a way to get the actual reference. I should edit the answer. – nawfal Oct 18 '15 at 18:45
  • 1
    It is still an open issue in GitHub: [SortedDictionary.First performance](https://github.com/dotnet/runtime/issues/18668). It seems that for some reason they don't like adding new functionality to existing collection types. – Theodor Zoulias Jun 13 '20 at 15:04
21

You can use LINQ:

var lastItem = sortedDict.Values.Last();

You can also get the last key:

var lastkey = sortedDict.Keys.Last();

You can even get the last key-value pair:

var lastKeyValuePair = sortedDict.Last();

This will give you a KeyValuePair<TKey, TValue> with Key and Value properties.

Note that this will throw an exception if the dictionary is empty; if you don't want that, call LastOrDefault.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • 10
    These methods likely trigger enumeration. I wonder if there is any way to get the last element (or element from any position index) without enumeration? Since the SortedDictionary is sorted into a tree, it could be in theory possible? – Roland Pihlakas Sep 25 '12 at 21:21
  • 1
    @RolandPihlakas: In theory, yes. In practice, I don't think so. – SLaks Sep 27 '12 at 02:35
  • 19
    For someone from a C++ background, this is hard to accept. Enumerating through the entire sorted dictionary just to get the last element is hopelessly inefficient. Are there more capable C# Collection libraries around? – avl_sweden Feb 26 '13 at 18:16
  • This is very old, indeed, however, someone may answer my question. Is it not possible to try *sortedDic.Keys[sortedDic.Keys.Count - 1]* instead, in order to get the last key? – ThunderGr Oct 04 '13 at 13:33
  • 2
    @ThunderGr: No; it doesn't have an indexer. – SLaks Oct 04 '13 at 13:43
  • You can create a class, inherit SortedDictionary, and override the Add-method. There you can call the original Add method, and save the last added element. Then just add a Last-property, which returns this last added element. – Coder14 Jan 17 '15 at 16:46
  • 2
    @Lander: That will completely break as soon as you remove an element. – SLaks Jan 18 '15 at 13:59
  • @SLaks: That's true. In my case elements are never removed, but we don't know if that's also the case in the situation of the OP. – Coder14 Jan 19 '15 at 21:04
2

You can use SortedDictionary.Values.Last();

or if you want the key and the value

SortedDictionary.Last();
kemiller2002
  • 113,795
  • 27
  • 197
  • 251
0

SortedList list...

list[ Keys[Keys.Count - 1] ];  // returns the last entry in list
Undo
  • 25,519
  • 37
  • 106
  • 129
0

As folks have already pointed Last extension will enumerate the entire collection, its impact on perf can be deadly. Just to remove 10000 last elements from SortedDict, it took a lot more time than similar operation on SortedSet.

  1. SortedSet Removal Elapsed ms : 8

  2. SortedDict Removal Elapsed ms : 3697

    // In below code,ss is SortedSet and sd is SortedDictionary and both contain same 10000 elements.

     sw.Start();
     while (ss.Count != 0)
     {
         ss.Remove(ss.Max);
     }
    
     sw.Stop();
     Console.WriteLine("SortedSet Removal Elapsed ms : {0}", sw.ElapsedMilliseconds);
    
     sw.Reset();
    
     sw.Start();
     while (sd.Count != 0)
     {
         sd.Remove(sd.Keys.Last());
     }
    
     sw.Stop();
     Console.WriteLine("Dict Removal Elapsed ms : {0}", sw.ElapsedMilliseconds);