Initial problem
For a project I found myself in the need to search for elements in a container that are the closest neighbours to another precise element I have. In my case it was points in any dimension, but it can apply to various other things that we are not used to compute "distance" for.
So I decided to write a generic function to perform this search, so I can use it for whatever type I want, provided that I can compute the "distance" between two elements of this type. I tried to make it in the style of standard library's algorithm.
My solution
template<typename T, class Distance>
struct Comp
{
using result_type = typename std::result_of<Distance(const T&, const T&)>::type;
using type = std::less<result_type>;
};
template<class InputIt, typename T, class Distance,
class Compare = typename Comp<T, Distance>::type>
std::vector<T> find_n_nearest(
InputIt start,
InputIt end,
const T& val,
size_t n,
Distance dist,
Compare comp = Compare())
{
std::vector<T> result{start, end};
std::sort(std::begin(result), std::end(result),
[&] (const T& t1, const T& t2)
{
return comp(dist(val, t1), dist(val, t2));
});
result.erase(std::begin(result) + n, std::end(result));
return result;
}
What this (the function) basically does is :
- create a copy of the range we want to look in
- sort it, by comparing values returned from the
distance
function betweenval
and the element of the range, so that we have the nearest neighbours ofval
at the begining of our vector - compare the distances with a custom operator if provided,
std::less
if not - only keep the
n
elements we want
Now about the boilerplate :
I want to default the Compare
type to std::less
, which needs a type parameter. So we need to find the return type of the Distance
object provided, and my solution here is what I found to work with both functions and lambdas.
Examples
Simple use case : retrieve the nearest values to an int. Distance between two ints will be the absolute value of their substraction.
const auto distance_int =
[] (int i, int j)
{
return std::abs(i-j);
};
std::vector<int> v = {56, 10, 79841651, 45, 59, 68, -20, 0, 36, 23, -3256};
auto res = {0, 10, 23, -20, 36};
auto found = find_n_nearest(std::begin(v), std::end(v), 4, 5, distance_int);
if(std::equal(std::begin(res), std::end(res), std::begin(found)))
std::cout << "Success !" << std::endl;
To illustrate the use of the comparator, let's say I now define my "neighbourhood" of ints as being as distant as possible. In a word, the opposite of the precedent example.
std::list<int> v = {56, 10, 79841651, 45, 59, 68, -20, 0, 36, 23, -3256};
auto res = {79841651, -3256, 68, 59, 56};
auto found = find_n_nearest(std::begin(v), std::end(v), 4, 5, distance_int, std::greater<int>());
if(std::equal(std::begin(res), std::end(res), std::begin(found)))
std::cout << "Success !" << std::endl;
Review
When I used this function, it was convenient to me to receive the results as a std::vector<T>
but this is not very good as a generic algorithm.
There are two problems with this : I think we should not alter the original range, so sorting it is not an option. Then we have to copy it elsewhere, and for that I used my cache vector (not that much time to think about it during the project, and it was not a critical piece of code). I thought of replacing this by providing an OutputIt
to the function, indicating where to put the results (for example an user-provided vector or whatever container), but I don't think I could sort the range in my algorithm, because the Output Iterator
concept is used only to ... output things.
If there are more efficient algorithms (instead of sorting from distance) to do it feel free to tell me, but that's not my main concern. I'd like to have an elegant solution, and pieces of advice on anything you think is not quite good in my code.
std::partial_sort
does exactly what you need. And I don't think it is a great burden for the caller to make an alterable copy prior to the call. \$\endgroup\$