Lower_bound == upper_bound

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

    float operator()(double f)
        {
        SpectrumPoint* l=std::lower_bound(beginGet(),endGet(),(SpectrumPoint){float(f),0.0f}
            ,SpectrumPoint::CompareFreqLessThan);
        if(l>beginGet())
            {--l;}

        SpectrumPoint* u=std::lower_bound(beginGet(),endGet(),(SpectrumPoint){float(f),0.0f}
            ,SpectrumPoint::CompareFreqLessThan);

        if(u==endGet())
            {u=beginGet();}

        if(l==u)
            {
            if(u==endGet())
                {return u->amp;}
            return l->amp;
            }

        double f_min=l->freq;
        double A_min=l->amp;
        double f_max=u->freq;
        double A_max=u->amp;

        double delta_f=f_max-f_min;
        double delta_A=A_max-A_min;

        return A_min + delta_A*(f-f_min)/delta_f;
        }

Прошу прощения за эту путаницу: - (

Что означает нижнее значение. Если бы мне пришлось угадать, я бы ответил, что эта функция возвращает итератор в последнем элементе, который меньше запрашиваемого значения. Но я вижу, что lower_bound почти совпадает с upper_bound. Единственное различие - строгое неравенство в случае upper_bound. Существует ли истинная функция выбора нижней границы в stl, которая согласуется с обычным определением нижней границы.

Ответ 1

  • Нижняя граница: первый элемент, который больше или равен.

  • Верхняя граница: первый элемент, который строго больше.

Пример:

+- lb(2) == ub(2)       +- lb(6)        +- lb(8)
|        == begin()     |  == ub(6)     |   +- ub(8) == end()
V                       V               V   V
+---+---+---+---+---+---+---+---+---+---+---+
| 3 | 4 | 4 | 4 | 4 | 5 | 7 | 7 | 7 | 7 | 8 |
+---+---+---+---+---+---+---+---+---+---+---+
    ^               ^                       ^
    |               |                       |
    +- lb(4)        +- ub(4)                +- lb(9) == ub(9) == end()

    |- eq-range(4) -|

Как вы можете видеть, полуоткрытый равный диапазон для n равен [lb (n), ub (n)).

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

void insert(Container & c, T const & t)
{
    auto it = std::lower_bound(c.begin(), c.end(), t);

    // if unique container:
    if (it != c.end() && *it == t) { /* error, element exists! */ return; }

    c.insert(it, t);
}

Ответ 2

Он возвращает итератор за последний элемент, который меньше запрашиваемого значения. Это полезно в качестве позиции вставки (и поэтому функция возвращает этот итератор). Также полезно, чтобы полуоткрытый диапазон first, lower_bound(first, last, value) указывал все значения меньше value.

upper_bound возвращает итератор за последний элемент [меньше или равно/не больше] запрашиваемого значения. Или строго: последний элемент, значение которого не меньше, поскольку оба алгоритма имеют дело исключительно с менее чем компараторами.

Если вам нужен итератор перед итератором, возвращаемым lower_bound, вы можете вычесть 1 (для итератора произвольного доступа), декремент (для двунаправленного итератора) или выполнить линейный поиск вместо использования lower_bound (для итератор, который не является ни одним из них).

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

Ответ 3

Так как это было открыто, я попытаюсь ответить на мой комментарий.

Имя lower_bound является математически неправильным. Лучшее имя может быть least_upper_bound, но это, вероятно, смутило бы большинство не-математически мыслящих людей. (И тогда, что вы называете upper_bound? almost_least_upper_bound? Yech!)

Мой совет: ознакомьтесь с тем, что имена lower_bound и upper_bound технически неверны. Две функции, как они определены, весьма полезны. Подумайте об этих функциях как о полезном использовании нотации.

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

Почему обратный итератор? Так же, как нет гарантии, что верхняя граница существует в контейнере, аналогично не гарантирует, что нижняя граница будет существовать. Существующие lower_bound и upper_bound возвращают end(), чтобы указать, что искомое значение неактивное. Истинная нижняя граница должна вернуть rend(), чтобы указать, что искомое значение неактивное.

Существует способ реализовать истинную нижнюю грань в виде форвардного итератора, но он стоит за цену, злоупотребляя значением end(), чтобы означать, что "нет нижней границы". Проблема с этим злоупотреблением нотацией заключается в том, что некоторый пользователь функции может сделать что-то эквивалентное true_lower_bound(off_scale_low_search_value)-1 и voila! у одного есть указатель на самый большой элемент в наборе.

Итак, вот как это сделать. Вернуть функцию нижней нижней границы end(), если контейнер пуст или если искомое значение меньше первого значения в контейнере. В противном случае верните upper_bound()-1.

Ответ 4

lower_bound, upper_bound и equal_range - это функции, которые выполняют двоичный поиск в отсортированной последовательности. Необходимость трех функций исходит из того, что элементы могут повторяться в последовательности:

1, 2, 3, 4, 4, 4, 5, 6, 7

В этом случае при поиске значения 4, lower_bound вернет итератор, указывающий на первый из трех элементов со значением 4, upper_bound вернет итератор, указывающий на элемент со значением 5, и equal_range вернет пару, содержащую эти два итератора.

Ответ 5

Auch!

Вы изменили исходный код или это ошибка копирования-вставки там с первого дня?

float operator()(double f)
{
    SpectrumPoint* l=std::lower_bound//...
...
    SpectrumPoint* u=std::lower_bound//...
...
}

В коде, который я прочитал сегодня, вы назначаете lower_bound как "l", так и "u".

Ответ 6

После ответа Дэвида Хаммена я попытался объяснить, почему мы часто не считаем имена lower_bound/upper_bound правильными или, по крайней мере, интуитивными.

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

sparse range queries

Код:

template< typename T, typename U >
auto infimum(std::map<T,U> const& ctr, T query)
{
    auto it = ctr.upper_bound(query);
    return it == ctr.begin() ? ctr.cend() : --it;
}

template< typename T, typename U >
bool is_in_interval(std::map<T,U> const& ctr, T query)
{
    auto inf = infimum(ctr, query);
    return inf == ctr.end() ? false : query <= inf->second;
}

https://ideone.com/jM8pt3

В основном, чтобы получить поведение "серых стрелок", нам нужно upper_bound - 1 поэтому это странно.

Позвольте мне перефразировать это немного: от имени lower_bound мы инстинктивно ожидаем returns-first-immediately-inferior-element lower_bound returns-first-immediately-inferior-element lower_bound (например, серые стрелки). Но мы получаем returns-first-immediately-superior-element return returns-first-immediately-superior-element for для lower_bound; и first-immediately-strictly-superior-element для upper_bound. Вот что удивительно.

Удивительно, что вы работаете с редкой последовательностью, подобной моему мысленному эксперименту на картинке выше. Но это имеет прекрасный смысл, когда вы думаете об этом в терминах "границ equal_range " в плотной последовательности, населенной плато, как красиво изображенный Керрек С.Б.

Тестовый код:

#include <map>
#include <cassert>
#include <iostream>

// .. paste infimum and is_in_interval here

int main()
{
    using std::cout;
    using Map = std::map<int,int>;
    Map intervals{{2,5}, {8,9}};

    auto red = infimum(intervals, 4);
    assert(red->first == 2);
    cout << "red->first " << red->first << "\n";

    auto green = infimum(intervals, 6);
    assert(green->first == 2);
    cout << "green->first " << green->first << "\n";

    auto pink = infimum(intervals, 8);
    assert(pink->first == 8);
    cout << "pink->first " << pink->first << "\n";

    auto yellow = infimum(intervals, 1);
    assert(yellow == intervals.cend());

    auto larger_than_all = infimum(intervals, 15);
    assert(larger_than_all->first == 8);

    bool red_is = is_in_interval(intervals, 4);
    cout << "red is in " << red_is << "\n";

    bool green_is = is_in_interval(intervals, 6);
    cout << "green is in " << green_is << "\n";

    bool pink_is = is_in_interval(intervals, 8);
    cout << "pink is in " << pink_is << "\n";

    bool yellow_is = is_in_interval(intervals, 1);
    cout << "yellow is in " << yellow_is << "\n";
}

результаты в

red->first 2
green->first 2
pink->first 8
red is in 1
green is in 0
pink is in 1
yellow is in 0

кажется в порядке.

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

играть с этим:
https://ideone.com/jM8pt3

Ответ 7

Другим использованием lower_bound и upper_bound является поиск диапазона равных элементов в контейнере, например.

std::vector<int> data = { 1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6 };

auto lower = std::lower_bound(data.begin(), data.end(), 4);
auto upper = std::upper_bound(lower, data.end(), 4);

std::copy(lower, upper, std::ostream_iterator<int>(std::cout, " "));