0

I wrote a C++ routine to find nearest double element in sorted array. Is there a way to speed up?

There are two branches based on the value of boolean reversed, if reversed it is sorted in the decreasing order.

 void findNearestNeighbourIndex_new(real_T value, real_T* x, int_T x_size, int_T& l_idx)
{
    l_idx = -1;

    bool reversed= (x[1] - x[0] < 0);

    if ((!reversed&& value <= x[0]) 
                  || (reversed&& value >= x[0])){ 
        // Value is before first position in x
        l_idx = 0;
    }
    else if ((!reversed&& value >= x[x_size - 1]) 
                    || (reversed&& value <= x[x_size - 1])){ 
        // Value is after last position in x
        l_idx = x_size - 2;
    }
    else // All other cases
    {
        if (reversed)
        {
            for (int i = 0; i < x_size - 1; ++i)
            {
                if (value <= x[i] && value > x[i + 1])
                {
                    l_idx = i;
                    break;
                }
            }
        }
        else{
            for (int i = 0; i < x_size - 1; ++i)  
            {
                if (value >= x[i] && value < x[i + 1])
                {
                    l_idx = i;
                    break;
                }
            }
        }   
    }
}

In this very case where array is sorted, I do not see a way to do better. So, with profiling i see that the comparison in if (value <= x[i] && value > x[i + 1]) is expensive.

EDIT

tried with lower_bound()

std::vector<real_T> x_vec(x, x + x_size);

l_idx = std::upper_bound(x_vec.begin(), x_vec.end(), value) - x_vec.begin() - 1;
kiriloff
  • 25,609
  • 37
  • 148
  • 229
  • 8
    Why aren't you using binary search? – imreal Jul 15 '15 at 13:09
  • Possible duplicate of: https://stackoverflow.com/questions/698520/search-for-nearest-value-in-an-array-of-doubles-in-c (where there is a [good answer](https://stackoverflow.com/a/701141/4669135)) – Gabriel Devillers Aug 29 '18 at 16:33

4 Answers4

12

You can use std::lower_bound to find a element equal or greater than requested, and then move iterator backwards and check preceding value too. This will use binary search and will cost O(log n), also this enables standard STL comparators and so on.

Galimov Albert
  • 7,269
  • 1
  • 24
  • 50
  • 1
    I need in fact index of nearest neighbor, not value – kiriloff Jul 15 '15 at 13:28
  • @octoback And where did you say that in your question. It is impolite to ask for X when you actually want Y. – NathanOliver Jul 15 '15 at 13:30
  • 2
    @octoback since you are working with iterators and its a std::vector - you can easily do iterator matematics (substract vector begin iterator to get the index). – Galimov Albert Jul 15 '15 at 13:31
  • 1
    i am converting array to vector then using lower_bound, and it is dramatically worsening performance – kiriloff Jul 15 '15 at 13:38
  • 1
    @octoback again, your question is titled "in sorted vector". Anyway, you can use lower_bound on raw sorted array, too. – Galimov Albert Jul 15 '15 at 13:40
  • yes, it is in sorted vector and with your solution overall routine is 5x more time consuming than original mine – kiriloff Jul 15 '15 at 13:42
  • @octoback sorry, i don't believe its 5x times slower. Can you post implementation somewhere? – Galimov Albert Jul 15 '15 at 13:43
  • my program is more complex than call to this method, i dont know how much slower is this very method. i post my code in post – kiriloff Jul 15 '15 at 13:45
  • @octoback If you're constructing the vector every single time you do a search, it's going to be very slow because of memory allocation overhead. As PSIAlt said, you don't need to make a vector, you can use lower_bound directly on the array. – Sneftel Jul 15 '15 at 13:57
  • what is the syntax then? `l_idx = std::upper_bound(x, x + x_size, value) - std::begin(x) - 1;` is not compiling – kiriloff Jul 15 '15 at 14:02
3

If you don't actually have an vector to use with upper_bound() you don't need to construct one as that is going to be an O(n) operation. upper_bound() will work with the array that you have. You can use:

l_idx = std::upper_bound(x, x + size, value) - x - 1;

Test case:

#include <iostream>
#include <functional>
#include <algorithm>

int main()
{
    const int size = 9;
    int x[9] = {1,2,3,4,5,6,7,8,9};

    auto pos = std::upper_bound(x, x + size, 5) - x;

    std::cout << "position: " << pos;

    return 0;
}

Output:

5

As the result of upper_bound() points us to 6(live example).

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • is - x here correct? with this expression, my tests are broken, so that they are not a substitute for the loop I have written. i would rather see smth in this flavor `l_idx = std::upper_bound(x_vec.begin(), x_vec.end(), value) - x_vec.begin() - 1;` but this is not compiling – kiriloff Jul 16 '15 at 10:43
  • @octoback I edited my answer to show you a working example with an array. In order to have it the way you want it you would need to convert the array to vector which is going to cost you. – NathanOliver Jul 16 '15 at 11:59
  • That code not works if the target value is over highest vector value. It is solved at the code I wrote in my answer – mathengineer May 20 '21 at 08:36
0

The way is to substract 1 to size (to make work over higest value) and 0.5 to target value to make it accurate:

#include <iostream>
#include <functional>
#include <algorithm>
using namespace std;

int main()
{
    float x[10] = { 2,3,4,5,6,7,8,9,10,11 },y;
    int size = sizeof(x) / sizeof(x[0]),pos;


    y = 4.1; pos = std::upper_bound(x, x + size - 1, y - 0.5) - x;
    std::cout << "position: " << pos << " target value=" << y << " upper_bound=" << x[pos] << endl;
    y = 4.9; pos = std::upper_bound(x, x + size - 1, y - 0.5) - x;
    std::cout << "position: " << pos << " target value=" << y << " upper_bound=" << x[pos] << endl;
    y = -0.5; pos = std::upper_bound(x, x + size - 1, y - 0.5) - x;
    std::cout << "position: " << pos << " target value=" << y << " upper_bound=" << x[pos] << endl;
    y = 100; pos = std::upper_bound(x, x + size - 1, y - 0.5) - x;
    std::cout << "position: " << pos << " target value=" << y << " upper_bound=" << x[pos] << endl;
    getchar();
    return 0;
}
mathengineer
  • 140
  • 6
-1

Implemented this helper routine

void findNearestNeighbourIndex_bin_search_new(real_T value, real_T* x,  
              int_T start, int_T stop, int_T& l_idx)
{
    int_T mid = ( stop - start ) / 2;

    if (value >= x[mid+1])
    {
        findNearestNeighbourIndex_bin_search_new(value, x, mid + 1, stop, l_idx);
    }
    else if (value < x[mid])
    {
        findNearestNeighbourIndex_bin_search_new(value, x, start, mid, l_idx);
    }
    else
    {
        l_idx = mid;
        return;
    }
}
kiriloff
  • 25,609
  • 37
  • 148
  • 229