I have a bunch of pairs of dates and monetary values in a SortedDictionary<DateTime, decimal>
, corresponding to loan balances calculated into the future at contract-defined compounding dates. Is there an efficient way to find a date key that is nearest to a given value? (Specifically, the nearest key less than or equal to the target). The point is to store only the data at the points when the value changed, but efficiently answer the question "what was the balance on x date?" for any date in range.
A similar question was asked ( What .NET dictionary supports a "find nearest key" operation? ) and the answer was "no" at the time, at least from the people who responded, but that was almost 3 years ago.
The question How to find point between two keys in sorted dictionary presents the obvious solution of naively iterating through all keys. I am wondering if any built-in framework function exists to take advantage of the fact that the keys are already indexed and sorted in memory -- or alternatively a built-in Framework collection class that would lend itself better to this kind of query.
Since SortedDictionary
is sorted on the key, you can create a sorted list of keys with
var keys = new List<DateTime>(dictionary.Keys);
and then efficiently perform binary search on it:
var index = keys.BinarySearch(key);
As the documentation says, if index
is positive or zero then the key exists; if it is negative, then ~index
is the index where key
would be found at if it existed. Therefore the index of the "immediately smaller" existing key is ~index - 1
. Make sure you handle correctly the edge case where key
is smaller than any of the existing keys and ~index - 1 == -1
.
Of course the above approach really only makes sense if keys
is built up once and then queried repeatedly; since it involves iterating over the whole sequence of keys and doing a binary search on top of that there's no point in trying this if you are only going to search once. In that case even naive iteration would be better.
As digEmAll correctly points out, you could also switch to SortedList<DateTime, decimal>
so that the Keys
collection implements IList<T>
(which SortedDictionary.Keys does not). That interface provides enough functionality to perform a binary search on it manually, so you could take e.g. this code and make it an extension method on IList<T>
.
You should also keep in mind that SortedList
performs worse than SortedDictionary
during construction if the items are not inserted in already-sorted order, although in this particular case it is highly likely that dates are inserted in chronological (sorted) order which would be perfect.
So, this doesn't directly answer your question, because you specifically asked for something built-in to the .NET framework, but facing a similar problem, I found the following solution to work best, and I wanted to post it here for other searchers.
I used 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 similar methods for successors) to easily find the nearest items to a key.
More useful in your case, I think, is the RangeFrom
/RangeTo
/RangeFromTo
methods that allow you to retrieve a range of key-value-pairs between keys.
Note that all of these methods can also be applied to the TreeDictionary<K, V>.Keys
collection, which allow you to work with only the keys as well.
It really is a very neat implementation, and something like it deserves to be in the BCL.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With