Контейнер диапазона C++ STL

Я ищу контейнер, который отображает двойные указатели на объекты. Однако каждый ключ — это просто диапазон двойников, которые соответствуют этому объекту.

Например, это может быть пара ключ/значение ‹(0.0 3.0), ptr> или ‹(3.5 10.0), ptr2>.

container[1.0] должен возвращать ptr, container[3.0] также должен возвращать ptr, а container[-1.0] не должен быть определен.

Есть ли какой-либо объект с подобным поведением по умолчанию или мне придется реализовать его самостоятельно?

Изменить

Вот фактический код, который я написал, может быть проще отладить/предложить совет по нему.

// Behavior: A range is defined mathematically as (min, max]

class dblRange
{
public:
    double min;
    double max;

    dblRange(double min, double max)
    {
        this->min = min;
        this->max = max;
    };

    dblRange(double val)
    {
        this->min = val;
        this->max = val;
    };

    int compare(const dblRange rhs)
    {
        // 1 if this > rhs
        // 0 if this == rhs
        //-1 if this < rhs
        if (rhs.min == rhs.max && min == max)
        {
            /*if (min > rhs.min)
                return 1;
            else if (min == rhs.min)
                return 0;
            else
                return -1;*/
            throw "You should not be comparing values like this. :(\n";
        }
        else if (rhs.max == rhs.min)
        {
            if (min > rhs.min) 
                return 1;
            else if (min <= rhs.min && max > rhs.min)
                return 0;
            else // (max <= rhs.min)
                return -1;
        }
        else if (min == max)
        {
            if (min >= rhs.max)
                return 1;
            else if (min < rhs.max && min >= rhs.min)
                return 0;
            else // if (min < rhs.min
                return -1;
        }

        // Check if the two ranges are equal:
        if (rhs.min == min && rhs.max == max)
        {
            return 0;
        }
        else if (rhs.min < min && rhs.max <= min)
        {
            // This is what happens if rhs is fully lower than this one.
            return 1;
        }
        else if (rhs.min > min && rhs.min >= max)
        {
            return -1;
        }
        else
        {
            // This means there's an undefined case. Ranges are overlapping, 
            // so comparisons don't work quite nicely.

            throw "Ranges are overlapping weirdly. :(\n";
        }
    };

    int compare(const dblRange rhs) const
    {
        // 1 if this > rhs
        // 0 if this == rhs
        //-1 if this < rhs
        if (rhs.min == rhs.max && min == max)
        {
            /*if (min > rhs.min)
                return 1;
            else if (min == rhs.min)
                return 0;
            else
                return -1;*/
            throw "You should not be comparing values like this. :(\n";
        }
        else if (rhs.max == rhs.min)
        {
            if (min > rhs.min) 
                return 1;
            else if (min <= rhs.min && max > rhs.min)
                return 0;
            else // (max <= rhs.min)
                return -1;
        }
        else if (min == max)
        {
            if (min >= rhs.max)
                return 1;
            else if (min < rhs.max && min >= rhs.min)
                return 0;
            else // if (min < rhs.min
                return -1;
        }

        // Check if the two ranges are equal:
        if (rhs.min == min && rhs.max == max)
        {
            return 0;
        }
        else if (rhs.min < min && rhs.max <= min)
        {
            // This is what happens if rhs is fully lower than this one.
            return 1;
        }
        else if (rhs.min > min && rhs.min >= max)
        {
            return -1;
        }
        else
        {
            // This means there's an undefined case. Ranges are overlapping, 
            // so comparisons don't work quite nicely.

            throw "Ranges are overlapping weirdly. :(\n";
        }
    };

    bool operator== (const dblRange rhs ) {return (*this).compare(rhs)==0;};
    bool operator== (const dblRange rhs ) const {return (*this).compare(rhs)==0;};
    bool operator!= (const dblRange rhs ) {return (*this).compare(rhs)!=0;};
    bool operator!= (const dblRange rhs ) const {return (*this).compare(rhs)!=0;};
    bool operator< (const dblRange rhs ) {return (*this).compare(rhs)<0;};
    bool operator< (const dblRange rhs ) const {return (*this).compare(rhs)<0;};
    bool operator> (const dblRange rhs ) {return (*this).compare(rhs)>0;};
    bool operator> (const dblRange rhs ) const {return (*this).compare(rhs)>0;};
    bool operator<= (const dblRange rhs ) {return (*this).compare(rhs)<=0;};
    bool operator<= (const dblRange rhs ) const {return (*this).compare(rhs)<=0;};
    bool operator>= (const dblRange rhs ) {return (*this).compare(rhs)>=0;};
    bool operator>= (const dblRange rhs ) const {return (*this).compare(rhs)>=0;};

};

Прямо сейчас у меня возникают проблемы с тем, что карта принимает двойное значение в качестве ключа, хотя операторы сравнения определены.

Вот некоторый управляющий код, который я использую, чтобы проверить, будет ли он работать:

std::map<dblRange, int> map;
map[dblRange(0,1)] = 1;
map[dblRange(1,4)] = 2;
map[dblRange(4,5)] = 3;

map[3.0] = 4;

person Andrei Krotkov    schedule 06.07.2009    source источник
comment
Все ваши функции-члены сравнения должны быть помечены как const.   -  person Eric    schedule 07.07.2009
comment
У меня есть два набора, но вместо того, чтобы дублировать здесь код, я выбрал один из них.   -  person Andrei Krotkov    schedule 07.07.2009
comment
Я считаю, что мой код теперь работает правильно - кто-нибудь может проверить?   -  person Andrei Krotkov    schedule 07.07.2009
comment
Общие комментарии, должны быть только методы сравнения (константная версия, так как она не меняет содержимое). Все бинарные операторы могут быть реализованы как свободные функции (что сделает их симметричными для компилятора) с точки зрения функции сравнения (если она общедоступна). compare должен получить аргумент «другой» в качестве ссылки. Избегайте сравнения чисел с плавающей запятой с ==, так как это может привести к ошибкам (одно и то же число может быть представлено по-разному в зависимости от того, как оно было рассчитано). Предпочитайте списки инициализации присваиванию в конструкторах. Для отладки выполните итерацию по карте и распечатайте все значения по порядку.   -  person David Rodríguez - dribeas    schedule 07.07.2009
comment
Спасибо, дрибеас. Сравнение в качестве ссылок - хороший момент. В этом случае числа с плавающей запятой загружаются из файла и никогда не вычисляются явно, поэтому == должно быть хорошо. Лично я предпочитаю присваивания спискам инициализации, а отладкой занимается IDE.   -  person Andrei Krotkov    schedule 07.07.2009
comment
предпочитает не входит в это. Во многих случаях списки инициализации корректны, а присваивания нет. Так что привыкайте. :)   -  person jalf    schedule 25.08.2009


Ответы (7)


Создайте класс DoubleRange для хранения двойного диапазона и реализуйте в нем операторы сравнения. Таким образом, std::map сделает все остальное за вас, используя класс DoubleRange в качестве ключа.

person Daniel Earwicker    schedule 06.07.2009
comment
Итак, более конкретно, что будет означать каждое сравнение? Будет ли оператор == с одной двойной проверкой, находится ли двойная в диапазоне? - person Andrei Krotkov; 07.07.2009
comment
Операторы должны сравнивать DoubleRanges друг с другом. - person Daniel Earwicker; 07.07.2009
comment
Как бы вы тогда получили доступ к карте на основе одного двойника? - person Andrei Krotkov; 07.07.2009
comment
Вы бы предоставили DoubleRange конструктор, который берет одно двойное число и присваивает его обоим концам диапазона, а make == возвращает true, если один диапазон перекрывается другим (ключи, хранящиеся в карте, должны быть неперекрывающимися). - person Daniel Earwicker; 07.07.2009
comment
myMap[5.3] заставит компилятор построить временный DoubleRange из значения 5.3, что позволит работать поиску. - person Daniel Earwicker; 07.07.2009
comment
operator‹ — это все, что должен использовать std::map — a == b должен быть эквивалентен !(a‹b) & !(b‹a). - person Alex Martelli; 07.07.2009
comment
Действительно, но если вы собираетесь определить тип, представляющий диапазон, который можно сравнивать с другими диапазонами, вы также можете тщательно подумать о том, что должна означать каждая стандартная операция сравнения. - person Daniel Earwicker; 07.07.2009
comment
Но если мы придирчивы, вы имеете в виду && вместо &, верно? :) - person Daniel Earwicker; 07.07.2009
comment
Это умно. Я пытался сравнить с двойником, но, похоже, он не хотел работать. - person Andrei Krotkov; 07.07.2009
comment
Вы должны быть осторожны с этим подходом, определение ‹ таким образом может привести к бесконечным циклам или неожиданной перезаписи, если ваши диапазоны перекрываются (и, поскольку это числа с плавающей запятой, если ваши конечные точки не могут быть точно представлены, у вас могут быть перекрывающиеся границы, даже если вы не ждите) - person Todd Gardner; 07.07.2009
comment
Очень хороший момент. Есть ли способ заставить программу использовать сравнение с двойным значением вместо специального случая min==max? - person Andrei Krotkov; 07.07.2009
comment
Также можно использовать непосредственно boost::numeric::interval. - person Jepessen; 06.12.2013

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

// Generic range, can be parametrized for any type (double, float, int...)
template< typename T >
class range
{
public:
    typedef T value_type;

    range( T const & center ) : min_( center ), max_( center ) {}
    range( T const & min, T const & max )
        : min_( min ), max_( max ) {}
    T min() const { return min_; }
    T max() const { return max_; }
private:
    T min_;
    T max_;
};

// Detection of outside of range to the left (smaller values):
//
// a range lhs is left (smaller) of another range if both lhs.min() and lhs.max() 
// are smaller than rhs.min().
template <typename T>
struct left_of_range : public std::binary_function< range<T>, range<T>, bool >
{
    bool operator()( range<T> const & lhs, range<T> const & rhs ) const
    {
        return lhs.min() < rhs.min()
            && lhs.max() <= rhs.min();
    }
};
int main()
{
    typedef std::map< range<double>, std::string, left_of_range<double> > map_type;

    map_type integer; // integer part of a decimal number:

    integer[ range<double>( 0.0, 1.0 ) ] = "zero";
    integer[ range<double>( 1.0, 2.0 ) ] = "one";
    integer[ range<double>( 2.0, 3.0 ) ] = "two";
    // ...

    std::cout << integer[ range<double>( 0.5 ) ] << std::endl; // zero
    std::cout << integer[ range<double>( 1.0 ) ] << std::endl; // one
    std::cout << integer[ 1.5 ] << std::endl; // one, again, implicit conversion kicks in
}

Вы должны быть осторожны с равенством и сравнениями между двойными значениями. Различные способы получения одного и того же значения (в реальном мире) могут давать несколько разные результаты с плавающей запятой.

person David Rodríguez - dribeas    schedule 06.07.2009

Лучше использовать структуру данных Интервальное дерево. Boost имеет реализацию в Interval Container Library

person capone    schedule 06.12.2013

Один из подходов состоит в том, чтобы заранее рассчитать «точки останова»:

typedef vector< tuple<double, double, foo*> > collisionlist_t;
const collisionlist_t vec;
vec.push_back(make_tuple(0.0, 3.0, ptr));
vec.push_back(make_tuple(3.5, 10.0, ptr2));
// sort 
std::map<double, foo*> range_lower_bounds;
for(collisionlist_t::const_iterator curr(vec.begin()), end(vec.end()); curr!=end; ++curr)
{
    /* if ranges are potentially overlapping, put some code here to handle it */
    range_lower_bounds[curr->get<0>()] = curr->get<2>();
    range_lower_bounds[curr->get<1>()] = NULL;
}

double x = // ...
std::map<double, foo*>::const_iterator citer = range_lower_bounds.lower_bound(x);
person Todd Gardner    schedule 06.07.2009
comment
Я рассматривал этот подход, но я хотел бы сделать гораздо более интуитивный подход, как видно из моего управляющего кода. Если я смогу заставить это работать правильно, я бы предпочел это. - person Andrei Krotkov; 07.07.2009

Еще одно предложение: используйте математическое преобразование для отображения индекса из REAL в INT, который можно сравнивать напрямую.

Если эти диапазоны многочисленны и плотны, существует также структура, известная как «дерево интервалов», которая может помочь.

person Brian    schedule 08.07.2009

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

эта реализация использует map::lower_bound и НЕ использует класс в качестве домена карты

map::lower_bound возвращает итератор к первому элементу на карте со значением ключа, равным или превышающим значение указанного ключа. (т.е. наименьший ключ больше или равен K. Неудачный выбор имен методов STL, поскольку это наименьшая верхняя граница K.)

template <class codomain>
class RangeMap : private std::map<double,std::pair<double,codomain>{
public:
    typedef double domain;
    typedef std::map<double,std::pair<double,codomain>:: super;
    typename super::value_type value_type;
protected:
    static domain& lower(const value_type& v){
        return v.first;
    }

    static domain& upper(const value_type& v){
        return v.second.first;
    }

    static codomain& v(const value_type& v){
        return v.second.second;
    }

public:

    static const domain& lower(const value_type& v){
        return v.first;
    }
    static const domain& upper(const value_type& v){
        return v.second.first;
    }
    static const codomain& v(const value_type& v){
        return v.second.second;
    }


    static bool is_point(const value_type& vf) {
        return lower(v) == upper(v);
    }

    static bool is_in(const domain& d,const value_type& vf) {
        return (lower(v) <= d) && (d <= upper(v));
    }


    const_iterator greatest_lower_bound(const domain& d)const {
        const_iterator j = super::lower_bound(d);
        if(j!=end() && j->first==d) return j;//d is the lh side of the closed interval
                                             //remember j->first >= d because it was lower but its the first
        if(j==begin()) return end();//d < all intervals
        --j;                        //back up
        return j;
    }
    const_iterator find(domain& d) {
        const_iterator j = greatest_lower_bound(d);
        if (is_in(j,d)) return j;
        return end();
    }
    iterator greatest_lower_bound(const domain& d) {
        iterator j = super::lower_bound(d);
        if(j!=end() && j->first==d) return j;//d is the lh side of the closed interval
                                             //remember j->first >= d because it was lower but its the first
        if(j==begin()) return end();//d < all intervals
        --j;                        //back up
        return j;
    }
    const_iterator find(domain& d) const{
        iterator j = greatest_lower_bound(d);
        if (is_in(j,d)) return j;
        return end();
    }  //so much for find(d) 
    iterator find(domain& d){
        iterator j = greatest_lower_bound(d);
        if (is_in(j,d)) return j;
        return end();
    }  //so much for find(d) 

     struct overlap: public std::exception{
     };
     bool erase(const double lhep,const double rhep);
     //you have a lot of work regarding splitting intervals erasing when overlapped
     //but that can all be done with erase, and insert below. 
     //erase may need to split too
     std::pair<iterator,bool>
     split_and_or_erase_intervals(const double lhep,
                                  const double rhep, 
                                  const codomain& cd);
     //the insert method - note the addition of the overwrtite 
     std::pair<iterator,bool>
     insert(const double lhep,const double rhep,const codomain& cd,bool overwrite_ok){
          if( find(lhep)!=end() || find(rhep)!=end() ) {
              if(overwrite_ok){
                 return split_and_or_erase_intervals(const double lhep,
                                                     const double rhep, 
                                                     const codomain& cd);
              }
              throw overlap();
          }
          return insert(value_type(lhep,pair<double,codomain>(rhep,cd)));
     }
 };
person pgast    schedule 25.08.2009

Если ваши интервалы не должны перекрываться, вы должны добавить дополнительный код для проверки этого свойства во время вставки. В частности, свойство, которое вы хотите подтвердить, заключается в том, что ваш новый интервал полностью лежит в пределах диапазона, который ранее был пустым. Самый простой способ сделать это — разрешить два типа диапазонов: "занято" и "пусто". Вы должны начать с создания одной «пустой» записи, которая охватывает весь используемый диапазон. Вставка нового «занятого» диапазона требует:

(1) найти какое-либо значение в новом диапазоне.
(2) убедиться, что возвращаемый диапазон пуст и полностью охватывает ваш новый диапазон. (Это было обязательным утверждением выше)
(3) изменить возвращенный пустой диапазон так, чтобы его конец находился в начале вашего нового диапазона. (4) вставьте новый пустой диапазон, который начинается с конца нового диапазона и заканчивается старым концом возвращаемого диапазона.
(5) вставляете новый диапазон, будучи уверенным, что он окружен пустыми диапазонами.
(6) При вставке нового занятого диапазона, который не имеет пустого пространства, отделяющего его от других занятых диапазонов, могут возникнуть дополнительные угловые случаи.

person jws    schedule 09.08.2012