20

I have dictionary containing key value pairs.

SortedDictionary<int,int> dictionary=new SortedDictionary<int,int>();
dictionary.Add(1,33);
dictionary.Add(2,20);
dictionary.Add(4,35);

I want to get previous key value pair from a known key value. In the above case, if I have key 4, then how can I get <2,20>?

nawfal
  • 70,104
  • 56
  • 326
  • 368
PramodChoudhari
  • 2,503
  • 12
  • 33
  • 46

7 Answers7

17

It's hard to implement this efficiently with a SortedDictionary<TKey, TValue> since it is implemented as a binary search tree that does not expose predecessors or successors.

You could of course just enumerate each KeyValuePair until you find the "known" key. With a little bit of LINQ, this would look like (assuming the key definitely exists and isn't the first key):

SortedDictionary<int, int> dictionary = ...
int knownKey = ...

var previousKvp = dictionary.TakeWhile(kvp => kvp.Key != knownKey)
                            .Last();

If those assumptions don't hold, you could do:

var maybePreviousKvp = dictionary.TakeWhile(kvp => kvp.Key != knownKey)
                                 .Cast<KeyValuePair<int, int>?>()
                                 .LastOrDefault();

(Check that maybePreviousKvp != null to ascertain that the previous KeyValuePair was retrieved successfully.)

But this isn't going to be efficient at all.


If feasible, consider using a SortedList<TKey, TValue> instead (obviously, this may not be possible if you can't take its slower inserts and deletes). This collection supports efficient key and value-retrieval by ordered index since it is implemented as a growable array. Then your query becomes as simple as:

SortedList<int, int> dictionary = ...
int knownKey = ...

int indexOfPrevious = dictionary.IndexOfKey(knownKey) - 1;

// if "known" key exists and isn't the first key
if(indexOfPrevious >= 0)
{
   // Wrap these in a KeyValuePair if necessary
   int previousKey = dictionary.Keys[indexOfPrevious];
   int previousValue = dictionary.Values[indexOfPrevious];      
}

IndexOfKey runs a binary search on the keys-list, running in O(log n) time. Everything else should run in constant time, meaning the entire operation should run in logarithmic time.


Otherwise, you'll have to implement yourself / find a BST collection that does expose predecessors / successors.

Ani
  • 111,048
  • 26
  • 262
  • 307
11

I was also looking for an answer to this problem, and I thought a better solution than all of the answers here is to use the TreeDictionary<K, V> from the C5 Collections (GitHub/NuGet), which is an implementation of a red-black tree.

It has Predecessor/TryPredecessor and WeakPredessor/TryWeakPredecessor methods (as well as equivalent methods for successors) which does exactly what you want.

For example:

TreeDictionary<int,int> dictionary = new TreeDictionary<int,int>();
dictionary.Add(1,33);
dictionary.Add(2,20);
dictionary.Add(4,35);

// applied to the dictionary itself, returns KeyValuePair<int,int>
var previousValue = dictionary.Predecessor(4);
Assert.Equals(previousValue.Key, 2);
Assert.Equals(previousValue.Value, 20);

// applied to the keys of the dictionary, returns key only
var previousKey = dictionary.Keys.Predecessor(4);
Assert.Equals(previousKey, 2);

// it is also possible to specify keys not in the dictionary
previousKey = dictionary.Keys.Predecessor(3);
Assert.Equals(previousKey, 2);
rikoe
  • 1,639
  • 1
  • 21
  • 29
  • 5
    In case somebody doesn't know what a weak predecessor is, it's the first item **either equal to or less than** the passed value. A (strict) predecessor is the item just **less than** the passed value. Similarly for successors. – nawfal Jun 11 '14 at 11:56
  • C5 collections are cool, but they have problems with the documentation for the up-to-day version, so save your time, clone the sources, and look at the source code. – Lev Jul 12 '20 at 11:19
3
KeyValuePair<int, int> lookingForThis = dictionary
  .Reverse()
  .SkipWhile(kvp => kvp.Key != 4)
  .Skip(1)
  .FirstOrDefault();
Amy B
  • 108,202
  • 21
  • 135
  • 185
  • +1. Would just like to point out that the OP's question doesn't really make sense. While this answer will return they pair before, the pair before will be continually changing as you add items to the dictionary. I think more clarification on what the OP is trying to do is needed – Rob Jan 18 '11 at 05:42
  • 2
    I think it should be `.SkipWhile(kvp => kvp.Key != 4).Skip(1)` – Gabe Jan 18 '11 at 06:09
1

You could loop through the dictionary and keep track of values, I guess. Something like this:

public int GetPreviousKey(int currentKey, SortedDictionary<int, int> dictionary)
{
    int previousKey = int.MinValue;
    foreach(KeyValuePair<int,int> pair in dictionary)
    {
        if(pair.Key == currentKey)
        {
            if(previousKey == int.MinValue)
            {
                throw new InvalidOperationException("There is no previous key.");
            }
            return previousKey;
        }
        else
        {
            previousKey = pair.Key;
        }
    }
}

However, this is a pretty odd operation to require. The fact that you need it might be pointing at a problem with your design.

Adam Lear
  • 38,111
  • 12
  • 81
  • 101
0

Dictionary<TKey,TValue> is unsorted, so there is no previous thing for some item. Instead you can use SortedDictionary<TKey,TValue>.

EDIT: Sorry I read the title only LOL.

If you have to use LINQ:

int givenKey = 4;
var previousItem = dict.Where((pair, index) => 
                                   index == dict.Count || 
                                   dict.ElementAt(index + 1).Key == givenKey)
                       .FirstOrDefault();
Cheng Chen
  • 42,509
  • 16
  • 113
  • 174
  • Tried using this in a function that accepted `dict` and `givenKey` as my Dictionary and the key I was starting from, and to return `previousItem` as a string, since I have a dictionary of ``. I get an error: `Cannot implicitly convert type 'System.Collections.Generic.KeyValuePair' to 'string'`. So this doesn't just return the key - I confirmed it returns the whole KeyValuePair. So in my case, I did `KeyValuePair tempKVP = MoveToPreviousKey(myDict, givenKey);` and then `string previousKey = tempKVP.Key;`. Worked great. HTH someone. – vapcguy Sep 22 '16 at 21:29
0

I would prefer to use linq if that's the case... try this it will surely work

SortedDictionary<int,int> dictionary = new SortedDictionary<int,int>();
dictionary.add(1, 33);
dictionary.add(2, 20);
dictionary.add(4, 35);

int SelectedKey = 4;

var ResutValue = ( from n in dictionary    
               where n.Key < TheSelectedKey
               select n.Value).Last();

this.txtResult.Text = ResultValue.ToString();
Carls Jr.
  • 3,088
  • 7
  • 37
  • 57
0

How about this? I havent tested it though, but should give something to start think in this direction. Hope it helps.

        int input = 4;
        List<int> lKeys = dictionary.Keys.ToList();
        int reqIndex = lKeys.IndexOf(input) - 1;
        int reqAnswer = dictionary[reqIndex];

test for other conditions like if (reqIndex != -1) etc..

franklins
  • 3,710
  • 6
  • 41
  • 56