Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binary search list of dates for largest date where date <= n

Given a list of dates in descending order, this code will find the largest date where the date is <= searchDate.

List<CurrencyHistoricExchangeRate> history = GetOrderedHistory();

foreach (var record in history)
{
    if (record.Date < searchDate)
    {
        return record ;
    }
}

How would I write a binary search function to replace this method? I'm struggling to implement it for an inexact comparison like this.

This method is called frequently, and can contain several thousand records which is why I wish to replace it with a binary search.

like image 980
Tom Gullen Avatar asked Mar 03 '16 13:03

Tom Gullen


People also ask

What is a binary search algorithm?

Search algorithm finding the position of a target value within a sorted array. In computer science, binary search, also known as half-interval search, logarithmic search, or binary chop, is a search algorithm that finds the position of a target value within a sorted array. Binary search compares the target value to the middle element of the array.

What is the time complexity of binary search?

A simple approach is to do linear search. The time complexity of above algorithm is O (n). Another approach to perform the same task is using Binary Search. Binary Search: Search a sorted array by repeatedly dividing the search interval in half. Begin with an interval covering the whole array.

How to search a sorted array of elements using binary search?

Given a sorted array arr [] of n elements, write a function to search a given element x in arr []. A simple approach is to do a linear search. The time complexity of the above algorithm is O (n). Another approach to perform the same task is using Binary Search. Binary Search: Search a sorted array by repeatedly dividing the search interval in half.

When is the worst case reached in a binary search?

This is because the worst case is reached when the search reaches the deepest level of the tree, and there are always levels in the tree for any binary search. The worst case may also be reached when the target element is not in the array. If is one less than a power of two, then this is always the case.


2 Answers

Given a sorted list, List<T>.BinarySearch actually helps you find the index of item which is "equal", or "larger than" your item (presuming an ascending list and a default comparer).

This method returns:

  • The zero-based index of item in the sorted List, if item is found;
  • or, a negative number that is the bitwise complement of the index of the next element that is larger than item
  • or, if there is no larger element, the bitwise complement of Count.

So, first you need an inverted comparer, because your items are sorted in reverse:

class CurrencyHistoricExchangeRateComparer : IComparer<CurrencyHistoricExchangeRate>
{
    public int Compare(CurrencyHistoricExchangeRate x, CurrencyHistoricExchangeRate y)
    {
        // this is just the opposite of the default DateTime comparer
        return -x.Date.CompareTo(y.Date);
    }
}

Then, you need to check if the item was actually found or not, and complement the result:

private static int FindIndex(List<CurrencyHistoricExchangeRate> list, DateTime dateTime)
{
    var comparer = new CurrencyHistoricExchangeRateComparer();
    var idx = list.BinarySearch(
        new CurrencyHistoricExchangeRate() { Date = dateTime }, comparer);

    // not found? then calculate the bitwise complement to 
    // get the index of the first larger element 
    // (this will evaluate to list.Count if there is no such element)
    return (idx < 0) ? ~idx : idx;
}

Interpreting these results should then be something like:

var idx = FindIndex(history, someDate);

CurrencyHistoricExchangeRate rate = null;
if (idx < history.Count)
    rate = history[idx];
else
    throw new InvalidOperationException($"there are no dates smaller than {someDate}");
like image 192
Groo Avatar answered Sep 24 '22 07:09

Groo


After playing round with it a bit I came up with this working solution:

if (history.First().Date <= date) return history.First();

var lowerIx = 0;
var upperIx = history.Count - 1;
while (true)
{
    var middleIndex = lowerIx + (upperIx - lowerIx) / 2;

    if (history[middleIndex].Date <= date)
    {
        upperIx = middleIndex;
    }
    else
    {
        lowerIx = middleIndex;
    }

    if (lowerIx + 1 == upperIx) break;
}
if(history[upperIx].Date > date) throw new Exception();
return history[upperIx];
like image 42
Tom Gullen Avatar answered Sep 23 '22 07:09

Tom Gullen