Быстрый способ STL найти вход, который дает максимальный результат функции? (непрерывные целочисленные входы)

Чтобы улучшить читаемость, я пытаюсь избавиться от привычки изобретать велосипед.

Проблема:

Рассмотрим функцию черного ящика Foo, которая имеет целое число в качестве входных и выходных данных. Мы хотим найти вход, который максимизирует выход. Учтите, что все возможные входные данные принадлежат одному непрерывному диапазону целых чисел; и что диапазон достаточно мал, чтобы мы могли попробовать каждый из них.

Скорость важна, поэтому мы не используем контейнеры. Даже если пользователь уже создал контейнер для всех возможных входных данных, вычисление следующего ввода (++input) все равно происходит примерно в 100 раз быстрее, чем получение его из памяти (промахи кеша).

Пример:

Диапазон: [5, 8)

Foo(5); // 19
Foo(6); // 72
Foo(7); // 31

Мы хотим сделать функцию, которая должна возвращать 6:

InputOfMaxOutputOnRange(5, 8, Foo); // 6

Индивидуальное решение:

template <typename T, typename Func>
T InputOfMaxOutputOnRange (T begin_range, T end_range, Func && Scorer)
{    
    // initialise:
    auto max_o = Scorer(begin_range);
    T i_of_max_o = begin_range;

    // now consider the rest of the range:
    ++begin_range;

    for (T i = begin_range; i < end_range; ++i)
    {
        auto output = Scorer(i);

        if (max_o < output)
        {
            max_o = output;
            i_of_max_o = i;
        }
    }
    
    return i_of_max_o;  
}

Вопрос:

Я использую подобные функции так часто, что думаю, должен быть способ STL для этого. Есть?

У тебя оба оба вектора с собой? Или, скорее, у вас есть связанный выходной вектор? Если нет, есть ли у вас функция? В любом случае, если у вас нет вектора, мы не сможем сказать, как получить 6 в качестве ответа.

Onyambu 22.12.2020 01:30

@Onyambu, вектора нет. в этом-то и дело. Это диапазон.

Elliott 22.12.2020 01:33

Значит, у вас нет связанного вывода заранее? У вас есть функция? Как вы получили 19,72,31?

Onyambu 22.12.2020 01:34

@Онямбу, верно. Там вызывается функция. В моем примере кода это LhsMoreThanRhs. Вы получаете результат от вызова функции.

Elliott 22.12.2020 01:38

если у вас есть функция, которая должна быть оптимизирована (в вашем случае — максимизирована), вам следует рассмотреть возможность использования пакета NLOPT или даже использовать метод brent в библиотеке повышения, чтобы иметь возможность решить эту проблему.

Onyambu 22.12.2020 01:42

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

Elliott 22.12.2020 01:44

Как сохраняется ассоциация?

Ted Lyngmo 22.12.2020 01:47

@TedLyngmo, здесь ничего не сохраняется. Там вообще нет контейнеров.

Elliott 22.12.2020 01:47

То, что у вас есть, уже достаточно. Поэтому я предложил только в случае возникновения проблемы. Для меня то, что вы предоставили, достаточно лаконично

Onyambu 22.12.2020 01:48

Хорошо, как вы связываете 5 с 19 и 6 с 72 и т. д.?

Ted Lyngmo 22.12.2020 01:48

@TedLyngmo, функция вычисляет это.

Elliott 22.12.2020 01:48

Итак, что-то вроде это было бы вариантом?

Ted Lyngmo 22.12.2020 01:49

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

Onyambu 22.12.2020 01:50

@Onyambu За исключением, возможно, части о том, чтобы не изобретать велосипед. Я не уверен, что OP думает о добавлении небольшого пользовательского итератора для использования с max_element.

Ted Lyngmo 22.12.2020 01:54

Да, это просто оболочка для любого типа, который вы хотели бы использовать в итераторе диапазона. Возможно, это встроено в библиотеку диапазонов С++ 20 - я не читал об этом.

Ted Lyngmo 22.12.2020 01:55

@TedLyngmo, я тупой. Я не видела, что ты написал rit. это умно, но меняет одну настройку на другую. Я бы с радостью проголосовал за интересный ответ.

Elliott 22.12.2020 01:57

@Onyambu Нет, контейнер не нужен. Проверьте мой пример.

Ted Lyngmo 22.12.2020 01:57

@Elliott Я надеюсь, что в стандарте C++ 20 есть что-то, что может обеспечить то же самое, что и rit, который я сделал - я смотрел ranges, но пока ничего не нашел.

Ted Lyngmo 22.12.2020 01:59

@TedLyngmo, ага. Я понимаю. Извините, я полностью пропустил это.

Onyambu 22.12.2020 01:59

@Elliott По-видимому, существует усиленная версия rit (как ответил Маршалл Клоу), поэтому я бы выбрал эту версию.

Ted Lyngmo 22.12.2020 02:02
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
20
1 310
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Вы можете использовать std::max_element, определенный в <algorithm>.

Это вернет итератор к максимальному элементу в указанном диапазоне. Вы можете получить индекс, используя std::distance.

Пример скопирован с сайта cppreference.

 std::vector<int> v{ 3, 1, -14, 1, 5, 9 }; 
 std::vector<int>::iterator result;
 
 result = std::max_element(v.begin(), v.end());
 std::cout << "max element at: " << std::distance(v.begin(), result) << '\n';

Спасибо за быстрый ответ, но это не одно и то же. Это слишком неэффективно.

Elliott 22.12.2020 01:18

@Elliott: Не знаю, насколько это неэффективно; в нем отсутствует запрошенная вами операция с ключом, поэтому это не полный ответ, но он совершенно эффективен.

ShadowRanger 22.12.2020 01:21

@Elliott Не могли бы вы рассказать подробнее? Ваш фрагмент кода, по сути, выполняет линейное сканирование элементов в диапазоне, что делает и max_element.

Jaebum 22.12.2020 01:21

@Elliott нужна перегрузка max_element, которая принимает компаратор, и передает его LhsMoreThanRhs туда.

Eugene 22.12.2020 01:32

@ Юджин, для больших диапазонов, скажем, от 0 до 40 000, это будет примерно в 100 раз медленнее. Так что это просто бесполезный вариант, даже если он делает мой код более читабельным.

Elliott 22.12.2020 01:42

В общем, алгоритмы STL работают с последовательностями значений, которые проходятся итераторами. Они также склонны возвращать итераторы. Это шаблон, который он использует.

Если вы делаете много подобных вещей, где ваша входная «последовательность» представляет собой последовательный список чисел, вам понадобится итератор, который «перебирает» последовательность (без какого-либо хранилища за ней) .

Небольшой поиск обнаружил Boost.CountingIterator, который, похоже, может делать то, что вы хотите. Я уверен, что есть и другие подобные.

Предупреждение - полностью непроверенный код

    auto iter = std::max_element(boost::counting_iterator<int>(5),
                                 boost::counting_iterator<int>(8),
          // a comparator that compares two elements
                                );
    return *iter; // should be '6'


Как заметили другие, std::max_element определяется, чтобы получить самый большой элемент в диапазоне.

В вашем случае «итератор» является целым числом, а результатом разыменования этого итератора является... некоторый результат, который не связан с вводом в явном виде (но, по-видимому, у вас есть способ получить его эффективно).

В этом случае я бы, вероятно, определил специализированный класс итератора, а затем использовал его с std::max_element:

#include <iostream>
#include <iterator>
#include <algorithm>

// your association function goes here. I've just done something
// where the relationship from input to output isn't necessarily
// immediately obvious
int association_function(int input) {
    int a = input * 65537 + 17;
    int b = a * a * a;
    return b % 127;
}

class yourIterator {
    int value;
public:
    // create an iterator from an int value
    explicit yourIterator(int value) : value(value) {}

    // "Deference" the iterator (get the associated value)
    int operator*() const { return association_function(value);  }

    // advance to the next value:
    yourIterator operator++(int) {
        yourIterator temp(value);
        ++value;
        return temp;
     }

     yourIterator &operator++() {
        ++value;
        return *this;
    }

    // compare to another iterator
    bool operator==(yourIterator const& other) const { return value == other.value; }
    bool operator!=(yourIterator const& other) const { return value != other.value; }

    // get the index of the current iterator:
    explicit operator int() const { return value; }
};

int main() {
    // For demo, print out all the values in a particular range:
    std::cout << "values in range: ";
    std::copy(yourIterator(5), yourIterator(10), std::ostream_iterator<int>(std::cout, "\t"));

    // Find the iterator that gives the largest value:
    yourIterator max = std::max_element(yourIterator(5), yourIterator(10));

    // print out the value and the index that gave it:
    std::cout << "\nLargest element: " << *max << "\n";
    std::cout << "index of largest element: " << static_cast<int>(max);
}

Когда я запускаю это, я получаю вывод следующим образом:

values in range: 64     90      105     60      33
Largest element: 105
index of largest element: 7

Итак, вроде работает корректно.

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

// pass association as a template parameter
template <class Map>
class mappingIterator {
    int value;
    // create an instance of that type:
    Map map;
public:
    // use the instance to map from iterator to value:
    int operator*() const { return map(value);  }

Затем вам нужно будет преобразовать вашу функцию ассоциации в форму, подходящую для использования в качестве параметра шаблона, например:

struct association_function {
    int operator()(int input) const {
        int a = input * 65537 + 17;
        int b = a * a * a;
        return b % 127;
    }
};

Затем в main вы, вероятно, захотите определить тип итератора в сочетании с функцией ассоциации:

    using It = mappingIterator<association_function>;
    It max = std::max_element(It(5), It(10));

Хороший! Я бы только изменил b с int на long, так как "a * a * a" может привести к переполнению.

Ronald Souza 22.12.2020 02:43

@RonaldSouza: эта функция является просто заполнителем, поэтому ее хорошая работа не имеет особого значения.

Jerry Coffin 22.12.2020 02:47
Ответ принят как подходящий

Диапазоны С++ 20 могут сделать это:

template<typename T, typename F>
T argmax_iota(T begin, T end, F &&score) { // can't really think of a good name for this; maybe it doesn't even deserve its own function
    return std::ranges::max(std::views::iota(begin, end), std::less{}, std::ref(score));
    // over the values in the range [begin, end) produced by counting (iota)...
    // find the one that produces the greatest value (max)...
    // when passed to the projection function score...
    // with those values under the ordering induced by std::less
}

Годболт

iota нигде не хранит весь диапазон. Итераторы в диапазоне содержат одно значение T, которое увеличивается при увеличении итератора.

Это то, что я искал, когда создавал свой собственный счетный итератор.

Ted Lyngmo 22.12.2020 03:48

Другие вопросы по теме