0

I am looking for a way to do a binary search over function results with a signature close to the following:

template <class ArgT,
          class ResultT,
          class TransformFunc,
          class Compare = std::less<ResultT>>
ArgT
result_upper_bound(ResultT searched, ArgT begin, ArgT end, TransformFunc tf, Compare comp);

The function is expected to return a first arg in the range [begin, end) which gives TransformFunc result that does not satisfy result > searched.

I have succeded in doing this task by implementing iterator over integers and using std::ranges::views::transform and std::ranges::upper_bound. The problim is that this solution does not work with clang becuse ranges library is not correctly supported by this compiler and will not be until clang 16 is released. By the way, the code is close to this:

...
auto argsRng = std::ranges::subrange<MyIntIt<uint64_t>>(
    MyIntIt<uint64_t>(0), MyIntIt<uint64_t>(Conditions::bigNumber));
const auto func = [](uint64_t index) {
    return Conditions::func(index);
};
auto transformed =
    std::ranges::views::transform(argsRng, func);
auto it = std::ranges::upper_bound(transformed, searchedArg);
...

boost::transform_iterator and boost::range::adaptors::transform are not exactly RandomAccess. That means their usage instead of std::ranges::views::transform makes std::lower_bound non-effective.

What are other ways of implementing this without writing binary search?

  • 1
    This is just a minor variation on `std::upper_bound`. If this is a homework assignment, then it's a safe bet that the student is expected to manually implement the algorithm directly, instead of using C++20 ranges, or other library resources. – Sam Varshavchik Dec 17 '22 at 18:10
  • @Sam Varshavchik , it is not a homework assignment. – Георгий Гуминов Dec 17 '22 at 18:16
  • 1
    Your `TransformFunc` looks to me like a projection function, which is already supported by the C++20 constrained algorithms. So I don't think you need to implement `result_upper_bound` in C++20, just call `ranges::upper_bound` with the appropriate arguments should be enough. – 康桓瑋 Dec 17 '22 at 18:17
  • @康桓瑋 , TransformFunc is user defined function, it is an argument. – Георгий Гуминов Dec 17 '22 at 18:20
  • 1
    @ГеоргийГуминов The projection function is a user-defined function, which is also the fourth argument of [`ranges::upper_bound`](https://en.cppreference.com/w/cpp/algorithm/ranges/upper_bound). – 康桓瑋 Dec 17 '22 at 18:22
  • Well, if it's not a homework assignment, then the only possibility I see is that it's a random coding puzzle from one of many prolific scam sites that guarantee that solving their useless coding puzzles transforms anyone into an instant C++ uberhacker. That, of course, is not true. – Sam Varshavchik Dec 17 '22 at 18:27
  • @SamVarshavchik I don't solve random coding puzzles from scam sites. – Георгий Гуминов Dec 17 '22 at 18:47
  • 1
    Ah yes, projection, one of the cool things about ranges. – Surt Dec 17 '22 at 18:48

1 Answers1

0

The following solution both compiles in clang++14 and gcc-12:

...
using IntIter = IntegerRandomAccessIterator<uint64_t>;
auto argsRng = boost::make_iterator_range<IntTter>(0, Conditions::bigNumber);
const auto func = [](uint64_t index) {
    return Conditions::func(index);
};
auto it = std::ranges::upper_bound(transformed, searchedArg, {}, func);
...

Implementation of IntegerRandomAccessIterator looks like this:

#include <boost/iterator/iterator_facade.hpp>

template <std::integral T>
class IntegerRandomAccessIterator
        : public boost::iterator_facade<
            IntegerRandomAccessIterator<T>, T,
            boost::random_access_traversal_tag, T> {
public:
    using type = IntegerRandomAccessIterator<T>;
public:
    IntegerRandomAccessIterator(T initial = 0) : _val(initial) {};
    IntegerRandomAccessIterator(const type& other) = default;
protected:
    std::ptrdiff_t
    distance_to(const type& rhs) const   { return rhs._val - _val; };
    void advance(std::ptrdiff_t n)       { _val += n; };
    T dereference() const                { return _val; };
    bool equal(const type& other) const  { return _val == other._val; };
    void increment()                     { ++_val; };
    void decrement()                     { --_val; };
public:
    T operator[](std::ptrdiff_t n) const { return _val + n; }
private:
    T _val;
private:
    friend class boost::iterators::iterator_core_access;
};

template <std::integral T>
struct std::iterator_traits<IntegerRandomAccessIterator<T>> {
    using iterator_category = std::random_access_iterator_tag;
    using difference_type = std::ptrdiff_t;
    using value_type = T;
    using reference = T;
};

It models std::random_access_iterator.

Standard library solution is

...
auto argsRng = std::ranges::iota_view(uint64_t{0}, Conditions::bigNumber);
const auto func = [](uint64_t index) {
    return Conditions::func(index);
};
auto it = std::ranges::upper_bound(transformed, searchedArg, {}, func);
...

So I prefer using first variant with IntegerRandomAccessIterator<uint64_t> and boost::make_iterator_range and to remove this replacing with std::ranges::iota_view when ranges will finally work in clang.