What is the fastest way to get all the keys between 2 keys in a SortedList?

前端 未结 3 1919
心在旅途
心在旅途 2021-01-18 11:11

Given a populated SortedList. I would like to get all the keys (or their index range, what should be a closed int interval (mis

相关标签:
3条回答
  • 2021-01-18 11:37

    You can solve the problem by running an adapted binary search on the Keys twice to find the indexes that bound the range of interest in the Keys collection.

    Since IList<T> does not offer binary search facilities you need to write your own. Fortunately, there is also the option of stealing the ready-made implementation from How to perform a binary search on IList.

    Here's an adapted version to find the lower bound:

    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;
    }
    

    To implement UpperBound, simply change

    if (comparisonResult <= 0)
    

    to

    if (comparisonResult < 0)
    

    It's now trivial to do this:

    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] */
    
    0 讨论(0)
  • 2021-01-18 11:39

    As the list is sorted you can use binary search to locate the endpoints of your interval. Worst case performance will be O(log n).

    0 讨论(0)
  • 2021-01-18 11:53

    I wondered why SortedList<TKey, TValue> doesn't provide a BinarySearch when it's already sorted by the keys. It also uses the method itself(f.e. in IndexOf), but the array used is a private field.

    So i've tried to create an extension method for this, have a look:

    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];
            }
        }
    }
    

    Create a sample 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);
    

    Now you can use the extension method above to get the range of keys between a low- and high-key:

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

    Result:

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

    Note that this is built from scratch and not really tested, but it could give you an idea anyway.

    0 讨论(0)
提交回复
热议问题