Thread overview
How to use lowerBound and upperBound effectively?
Jul 02, 2019
A. Bressan
Jul 02, 2019
Ali Çehreli
Jul 02, 2019
A. Bressan
July 02, 2019
Hi, I am translating the following C++ code to D and I have some trouble to achieve it using ranges.

#include <vector>
#include <algorithm>

typedef std::vector<int>::const_iterator iter;
std::pair<iter,iter> getSubVector(iter beg, iter end, int val, int shift)
{
return {std::upper_bound(beg,end,val)-shift,end};
}

My best result is the following convoluted code, is there a better way?

auto getSubVector(T)(const ref T vec, int val, int shift)
if (isInstanceOf!(SortedRange, T))
{
auto begin=vec.length-assumeSorted!"a>b"(retro(vec)).lowerBound().length-shift;
return vec[begin..$];
}

The difficulty I have is that, contrary to C++, lowerBound and upperBound give the same piece of information because they return complementary sub-ranges.
To get the elements that are equal to val outside of the upperBound I need to reverse the range.

I tried to use lowerBound on a range sorted by "a<=b", but it triggers an error that does not seem to be relevant to binary search strategies:

std/algorithm/sorting.d(178): Predicate for isSorted is not antisymmetric. Both pred(a, b) and pred(b, a) are true for certain values.

July 02, 2019
On 07/02/2019 02:27 AM, A. Bressan wrote:

> contrary to C++, lowerBound and
> upperBound give the same piece of information because they return
> complementary sub-ranges.

I don't understand the specific problem but can 'trisect' be useful?

  https://dlang.org/library/std/range/sorted_range.trisect.html

Ali

July 02, 2019
On Tuesday, 2 July 2019 at 17:07:25 UTC, Ali Çehreli wrote:
> On 07/02/2019 02:27 AM, A. Bressan wrote:
>
> > contrary to C++, lowerBound and
> > upperBound give the same piece of information because they
> return
> > complementary sub-ranges.
>
> I don't understand the specific problem but can 'trisect' be useful?
>
>   https://dlang.org/library/std/range/sorted_range.trisect.html
>
> Ali

I try to clarify the problem, sorry for not being clearer.

Given a value 'v', 'std::lower_bound' returns an iterator pointing to the first element 'a' for which 'a<v' is false. 'std::upper_bound' returns an iterator to the first element 'a' between a pair of iterators for which 'a<=v' is false. Thus the two C++ functions find a different point.

The D function 'lowerBound' returns a range containing all elements '<v'. It is possible to recover the position of the first element '>=v' by using length.
'upperBound' returns a range containing all elements '>=v'. Again it is possible to recover the position of the first element '>=v' by subtracting the length of the returned range from the original length.
Thus 'lowerBound' and 'upperBound' provide the same piece of information: the position of the first element '>=v'. I need the position of the first element '>v'.

v=5
data              [1,2,3,4,5,5,6,7,8,9]
std::lower_bound           ^
std::upper_bound               ^
lowerBound         _______
upperBound                 ___________


My current solution is to reverse the range and the ordering so that I can pin-point the correct location.

v=5
data              [9,8,7,6,5,5,4,3,2,1]
std::lower_bound           ^
std::upper_bound               ^
lowerBound         _______
upperBound                 ___________

My question is motivated by the fact that the search policies (except the linear scan)  are variants of the bisection method https://en.wikipedia.org/wiki/Bisection_method
and can find both the first element '>=v' or the first element '>v' by changing the predicate '>=' for '>'. It seems to me that there must be a simpler way and that I am overlooking something.

The 'trisect' method, provides both the position of first element '>=v' and that of  the first element '>v'. So it provides more than what I need, but it is nicer to read and maybe faster than my solution.

Thanks

Andrea