Могу ли я использовать std :: lower_bound для вызовов функций вместо итераторов? - PullRequest
2 голосов
/ 19 апреля 2020

Простой вариант использования: я хочу найти с помощью бинарного поиска минимальный индекс i, для которого f(i)>=t, где t - некоторый порог, а f - монотонно возрастающая функция над целочисленными значениями.

Простой способ go состоит в том, чтобы просто вызывать эту функцию для каждого возможного ввода, сохранять ее в контейнере и затем использовать lower_bound, это будет полезно в сценарии, где я хочу выполнить несколько поисков для одного и того же функция с различными пороговыми значениями.

Однако в моем сценарии оценки функций являются дорогостоящими, и у меня есть несколько различных функций / лямбд, для которых я выполняю только один двоичный поиск.

Так что я предполагаю, что мне нужно, это либо lower_bound функция, принимающая функцию и диапазон значений вместо начального и конечного итераторов, либо мне нужен механизм, чтобы скрыть вызовы функций внутри структуры итератора. Я знаю, что первое решение легко реализовать, но я надеялся, что это решение поможет избежать реализации бинарного поиска с нуля.

Мне показалось, что это общий случай использования, но почему-то я ничего не смог найти по этому вопросу. особая проблема в сети. Буду признателен за любые советы, хитрости и ссылки.

РЕДАКТИРОВАТЬ

Я нашел два первых представленных решения очень интригующим. Данное решение с использованием аргумента comp очень элегантно, однако я забыл упомянуть причину, по которой этот подход не работает для меня. В дополнение к длительному времени выполнения для оценки одной функции у меня также есть проблема с большим пространством поиска (например, более 1022 * целых чисел), что делает нецелесообразным выделение фиктивного вектора для этой цели. Я не знаю, как это будет работать с boost итераторами приращений, но мне все равно нужно что-то для работы только с std.

Хотя второе решение с использованием пользовательского итератора довольно многословно. Я проверил это с большим числом, которое я упомянул (и изменил int s на long long s), но это, кажется, также медленно. Кажется, что lower_bound на самом деле вызывает operator++ несколько раз go из одного местоположения в другое, поэтому реализация std::lower_bound уже может быть убийцей для моего подхода здесь (см. Ниже пример измененного кода и вывод ) и нет никакой возможности обойти пользовательскую реализацию (которая у меня уже есть, здесь нет необходимости).

Спасибо, однако, за понимание, оба ответа показали мне что-то новое. И, может быть, кто-то может пролить немного света на упомянутые выше моменты, поскольку я определенно не эксперт по итераторам или реализации lower_bound, возможно, я использовал это неправильно или что-то в примере кода, приведенном @idclev, делает его итерируйте это неэффективно через числа, которые я не узнал.

Пример модифицированного кода

#include <iostream>
#include <unordered_map>
#include <algorithm>

long long foo(long long i){ std::cout << "function evaluation:\t" << i << std::endl; return i;}

using function_type = long long(*)(long long);

template <function_type F>
struct fun_iterator {

    using difference_type = size_t;
    using value_type = long long;
    using pointer = int*;
    using reference = int&;
    using iterator_category = std::forward_iterator_tag;

    static std::unordered_map<long long,long long> m;
    long long index;
    fun_iterator(long long index) : index(index) {}

    fun_iterator& operator++() {
        std::cout << "operator++:\t" << index << std::endl;
        ++index;
        return *this;
    }
    fun_iterator operator++(int x) {
        fun_iterator it = *this;
        ++index;
        return it;
    }
    int operator*() {
        auto it = m.find(index);
        if (it != m.end()) return it->second;
        auto res = F(index);
        m[index] = res;
        return res;
    }
    bool operator!=(const fun_iterator& other){
        return index != other.index;
    }
    bool operator==(const fun_iterator& other){
        return index == other.index;
    }
    bool operator<(const fun_iterator& other){
        return index < other.index;
    }
};

template <function_type F>
std::unordered_map<long long,long long> fun_iterator<F>::m;

template <function_type F>
std::pair<fun_iterator<F>,fun_iterator<F>> make_begin_and_end(long long begin,long long end){
    return {{begin},{end}};
}

int main() {
    auto x = make_begin_and_end<foo>(0,10L);
    auto it = std::lower_bound(x.first,x.second,4L);
    // auto x = make_begin_and_end<foo>(0,1000000000000L);
    // auto it = std::lower_bound(x.first,x.second,400000000000L);
    std::cout << it.index << std::endl;
}

и вывод:

operator++: 0
operator++: 1
operator++: 2
operator++: 3
operator++: 4
operator++: 5
operator++: 6
operator++: 7
operator++: 8
operator++: 9
operator++: 0
operator++: 1
operator++: 2
operator++: 3
operator++: 4
function evaluation:    5
operator++: 0
operator++: 1
function evaluation:    2
operator++: 2
operator++: 3
function evaluation:    4
function evaluation:    3
operator++: 3
4

Ответы [ 2 ]

1 голос
/ 19 апреля 2020

Как предложил @KamilCuk, напишите свой собственный итератор или, альтернативно,

Вы можете взять любой контейнер натуральных чисел (если у вас нет под рукой диапазонов, просто создайте std::vector<int> и заполните его монотонно растущие числа - при условии, что вы хотя бы знаете границы ожидаемого интервала, в котором находится ваше ожидаемое значение). Затем std::lower_bound принимает аргумент comp:

std::vector<int> args(1000);
std::iota(args.begin(), args.end(), 0);
root = std::lower_bound(args.cbegin(), args.cend(), t,
        [](int x, int t){ return f(x) < t; });

(В качестве проверки работоспособности проверьте, начинается ли root args - тогда 0 может быть выше, чем вы хотите root - или это конец args - тогда root выше расчетной правой границы.)

0 голосов
/ 19 апреля 2020

Вы можете написать свой собственный итератор и, чтобы избежать ненужных оценок функции, вы можете использовать памятку. Итератор может иметь отображение c для кэширования результатов вызовов функций. Чтобы сделать итераторы для разных функций другого типа, я параметризовал итератор по указателю на функцию:

#include <iostream>
#include <unordered_map>
#include <algorithm>

double foo(int i){ return i;}

using function_type = double(*)(int);

template <function_type F>
struct fun_iterator {

    using difference_type = size_t;
    using value_type = int;
    using pointer = int*;
    using reference = int&;
    using iterator_category = std::forward_iterator_tag;

    static std::unordered_map<int,double> m;
    int index;
    fun_iterator(int index) : index(index) {}

    fun_iterator& operator++() {
        ++index;
        return *this;
    }
    fun_iterator operator++(int x) { 
        fun_iterator it = *this;
        ++index;
        return it;
    }
    int operator*() { 
        auto it = m.find(index);
        if (it != m.end()) return it->second;
        auto res = F(index);
        m[index] = res;
        return res;
    }
    bool operator!=(const fun_iterator& other){
        return index != other.index;
    }
    bool operator==(const fun_iterator& other){
        return index == other.index;
    }
    bool operator<(const fun_iterator& other){
        return index < other.index;
    }
};

template <function_type F>
std::unordered_map<int,double> fun_iterator<F>::m;

template <function_type F>
std::pair<fun_iterator<F>,fun_iterator<F>> make_begin_and_end(int begin,int end){
    return {{begin},{end}};
}

int main() {
    auto x = make_begin_and_end<foo>(0,100);
    auto it = std::lower_bound(x.first,x.second,50);
    std::cout << it.index;
}

Карта будет использоваться повторно для последующих экземпляров fun_iterator для той же функции. И поскольку он параметризован на указателе функции, fun_iterator для другой функции использует другую карту для хранения результатов.

...