获取SortedList中两个键之间所有键的最快方法是什么?

时间:2014-05-22 12:02:51

标签: c# .net

给定填充的SortedList<DateTime, double>。对于给定的低和高日期时间间隔,我想得到所有(或它们的索引范围,应该是一个封闭的int间隔(错过了什么?))。

注意:不需要低值和高值实际上在SortedList中。


如果有人在没有SortedList的情况下更好地了解如何做到这一点,那么我想要做的范围有点广泛,我发现SortedList可能是合适的:

  • 我想使用DateTime密钥缓存双精度。
  • 具有给定密钥的double的访问性能优于添加密钥和删除密钥的性能
  • 这是事情:我必须&#34;无效&#34;给定键范围的缓存(删除键)同样不能保证在缓存中找到范围最小值和最大值。

3 个答案:

答案 0 :(得分:4)

我想知道为什么SortedList<TKey, TValue>在已经按键排序时没有提供BinarySearch。它也使用方法本身(例如IndexOf),但使用的数组是私有字段。

所以我试图为此创建一个扩展方法,看看:

public static class SortedListExtensions
{
    public static int BinarySearch<TKey, TValue>(this SortedList<TKey, TValue> sortedList, TKey keyToFind, IComparer<TKey> comparer = null)
    {
        // need to create an array because SortedList.keys is a private array
        var keys = sortedList.Keys;
        TKey[] keyArray = new TKey[keys.Count];
        for (int i = 0; i < keyArray.Length; i++)
            keyArray[i] = keys[i];

        if(comparer == null) comparer = Comparer<TKey>.Default;
        int index = Array.BinarySearch<TKey>(keyArray, keyToFind, comparer);
        return index;
    }

    public static IEnumerable<TKey> GetKeyRangeBetween<TKey, TValue>(this SortedList<TKey, TValue> sortedList, TKey low, TKey high, IComparer<TKey> comparer = null)
    {
        int lowIndex = sortedList.BinarySearch(low, comparer);
        if (lowIndex < 0)
        {
            // list doesn't contain the key, find nearest behind
            // If not found, BinarySearch returns the complement of the index
            lowIndex = ~lowIndex;
        }

        int highIndex = sortedList.BinarySearch(high, comparer);
        if (highIndex < 0)
        {
            // list doesn't contain the key, find nearest before
            // If not found, BinarySearch returns the complement of the index
            highIndex = ~highIndex - 1;
        }

        var keys = sortedList.Keys;
        for (int i = lowIndex; i < highIndex; i++)
        {
            yield return keys[i];
        }
    }
}

创建示例SortedList

DateTime start = DateTime.Today.AddDays(-50);
var sortedList = new SortedList<DateTime, string>();
for(int i = 0; i < 50; i+=2)
{
    var dt = start.AddDays(i);
    sortedList.Add(dt, string.Format("Date #{0}: {1}", i, dt.ToShortDateString()));
}

DateTime low = start.AddDays(1);   // is not in the SortedList which contains only every second day
DateTime high = start.AddDays(10);

现在您可以使用上面的扩展方法来获取低键和高键之间的键范围:

IEnumerable<DateTime> dateRange = sortedList.GetKeyRangeBetween(low, high).ToList();

结果:

04/04/2014
04/06/2014
04/08/2014
04/10/2014

请注意,这是从头开始构建的,并没有经过实际测试,但无论如何它都可以给你一个想法。

答案 1 :(得分:3)

在列表排序后,您可以使用binary search来查找间隔的端点。最差情况表现为O(log n)。

答案 2 :(得分:2)

您可以通过在Keys上运行适当的二进制搜索两次来解决问题,以找到绑定Keys集合中感兴趣范围的索引。

由于IList<T>不提供二进制搜索工具,您需要自己编写。幸运的是,还可以选择从How to perform a binary search on IList窃取现成的实现。

这是一个找到下限的改编版本:

public static int LowerBound<T>(this IList<T> list, T value, IComparer<T> comparer = null)
{
    if (list == null)
        throw new ArgumentNullException("list");

    comparer = comparer ?? Comparer<T>.Default;

    int lower = 0, upper = list.Count - 1;

    while (lower <= upper)
    {
        int middle = lower + (upper - lower) / 2;
        int comparisonResult = comparer.Compare(value, list[middle]);

        // slightly adapted here
        if (comparisonResult <= 0)
            upper = middle - 1;
        else
            lower = middle + 1;
    }

    return lower;
}

要实施UpperBound,只需更改

即可
if (comparisonResult <= 0)

if (comparisonResult < 0)

现在这样做是微不足道的:

var low = set.Keys.LowerBound(value);
var high = set.Keys.UpperBound(value);

// These extra comparisons are required because the adapted binary search
// does not tell us if it actually found the needle. They could be rolled
// into the methods themselves, but this would require another out parameter.
if (set.Keys[low] != value) ++low;
if (set.Keys[high] != value) --high;

if (low <= high) /* remove keys in the range [low, high] */