有效地找到最近的字典键

时间:2012-09-13 18:51:32

标签: c# sorting dictionary

我在SortedDictionary<DateTime, decimal>中有一堆日期和货币值,对应于在合约定义的复利日期计算到未来的贷款余额。有没有一种有效的方法来查找最接近给定值的日期键? (具体而言,最近的键小于或等于目标)。关键是在值改变时仅存储数据,但有效地回答“x日期的余额是什么?”的问题。对于范围内的任何日期。

问了一个类似的问题(What .NET dictionary supports a "find nearest key" operation?),当时答案是“不”,至少来自回复的人,但差不多是3年前。

问题How to find point between two keys in sorted dictionary提出了天真地遍历所有键的明显解决方案。我想知道是否存在任何内置框架函数来利用密钥已经在内存中索引和排序的事实 - 或者是内置的Framework集合类,它可以更好地适应这种查询。 / p>

4 个答案:

答案 0 :(得分:23)

由于SortedDictionary按键排序,您可以使用

创建按键排序列表
var keys = new List<DateTime>(dictionary.Keys);

然后在其上高效执行binary search

var index = keys.BinarySearch(key);

正如文档所说,如果index为正或零,则密钥存在;如果它是否定的,则~indexkey所在的索引(如果它存在的话)。因此,“立即更小”的现有密钥的索引是~index - 1。确保正确处理key小于任何现有密钥和~index - 1 == -1的边缘情况。

当然,如果keys建立一次然后重复查询,上述方法才有意义;因为它涉及迭代整个键序列并进行二进制搜索,如果你只想搜索一次就没有意义。在这种情况下,即使是天真的迭代也会更好。

更新

正如digEmAll正确指出的那样,您也可以切换到SortedList<DateTime, decimal>,以便Keys集合实现IList<T>(SortedDictionary.Keys不会)。该界面提供了足够的功能,可以手动对其执行二进制搜索,因此您可以采用this code并将其作为IList<T>上的扩展方法。

您还应该记住,如果项目未按已排序的顺序插入,SortedList在构造期间的表现会比SortedDictionary更差,尽管在这种特殊情况下很可能会插入日期按时间顺序排序,这将是完美的。

答案 1 :(得分:8)

所以,这并没有直接回答你的问题,因为你特意要求.NET框架内置的东西,但面对类似的问题,我发现以下解决方案效果最好,我想发布它这里是其他搜索者。

我使用了TreeDictionary<K, V>C5 Collections / GitHub)中的NuGet,这是一个红黑树的实现。

它有Predecessor / TryPredecessorWeakPredessor / TryWeakPredecessor方法(以及类似的后继方法),可以轻松找到最近的项目。

我认为,在您的情况下更有用的是RangeFrom / RangeTo / RangeFromTo方法,它们允许您检索键之间的一系列键值对。

请注意,所有这些方法也可以应用于TreeDictionary<K, V>.Keys集合,这样您也可以只使用这些键。

这真的是一个非常简洁的实现,类似的东西应该在BCL中。

答案 2 :(得分:1)

无法使用SortedListSortedDictionary或任何其他&#34;内置&#34;高效查找最近的密钥。 .NET类型,如果您需要将查询与插入交错(除非您的数据到达预先排序,或者集合总是很小)。

正如我在你引用的另一个问题上提到的,我创建了三个与B +树相关的数据结构,为任何可排序的数据类型提供了find-nearest-key功能:BList<T>, BDictionary<K,V> and BMultiMap<K,V>。这些数据结构中的每一个都提供了{C} FindLowerBound()FindUpperBound()的{​​{1}}和lower_bound方法。

答案 3 :(得分:0)

    public static DateTime RoundDown(DateTime dateTime)
    {
        long remainingTicks = dateTime.Ticks % PeriodLength.Ticks;
        return dateTime - new TimeSpan(remainingTicks);
    }