найти в контейнере объекты чистых виртуальных классов

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

set<unique_ptr<interval>> db;

Чтобы гарантировать неперекрывающееся свойство, определено:

bool operator<(const unique_ptr<interval>& lhs, 
               const unique_ptr<interval>& rhs);

Класс interval имеет 2 поля: start, last, поэтому я могу определить, попадает ли какой-нибудь int в диапазон определенного interval экземпляра.

Теперь у меня есть int n, который я хочу найти в наборе, чтобы определить, в каком интервале он содержится.

Я думал о создании unique_ptr<interval> dummy_interval как с first=last=n, так и с поисковым вызовом db.find(dummy_interval), но проблема в том, что класс interval является чисто виртуальным, и поэтому я не могу создать ни одного его экземпляра.

Как я могу это преодолеть?


person Hanna Khalil    schedule 26.09.2016    source источник
comment
получить класс фиктивного интервала и использовать его. Или, может быть, переосмыслите свой дизайн, чтобы он не понадобился.   -  person Hayt    schedule 26.09.2016
comment
то, что вы имеете в виду, не может создать ни одного экземпляра. Можете ли вы унаследовать интервальный класс или нет?   -  person    schedule 26.09.2016
comment
@Hayt Я мог бы получить класс, но я считаю, что это перебор. Переосмыслить дизайн? Буду рад услышать предложения   -  person Hanna Khalil    schedule 26.09.2016
comment
для редизайна потребовалась бы информация о том, почему это вообще так, и это стало бы слишком широким. Альтернативой было бы не делать базовый класс абстрактным, но мне кажется, что это подкласс, где first = last было бы самым простым решением.   -  person Hayt    schedule 26.09.2016
comment
Проблема с вашим подходом заключается в том, что элементы сами по себе являются ключами, поэтому поиск ключа означает, что вам нужен элемент, чего вы не можете сделать. Я предлагаю рефакторинг, в котором ключи и значения разделены, и тогда ваш контейнер будет map или unordered_map, и вы можете легко искать по ключу. Они также обеспечивают логарифмический и амортизированный доступ к постоянному времени соответственно, поэтому вы ничего не теряете, кроме отхода от set   -  person AndyG    schedule 26.09.2016
comment
@AndyG Я думал об использовании unordered_map, но меня обескуражили из-за того, что у меня будет избыточная информация. диапазон будет указан в ключе, а также в значении, которое является обработчиком   -  person Hanna Khalil    schedule 27.09.2016


Ответы (1)


Поскольку у вас есть неперекрывающиеся интервалы, вы можете использовать std::lower_bound с настраиваемым компаратором:

template <typename It>
It find_interval(It first, It last, int value) {
    // See explanation below.
    auto it = std::lower_bound(first, last, value, 
                               [](const std::unique_ptr<interval>& i1, int value) {
                                   return i1->start < value;
                               });
    if (it != last && (*it)->start == value) {
        return it;
    }
    --it;
    // Change this to: (*it)->end > value ? it : last
    // ...if the upper bound of the interval are not included.
    return (*it)->end < value ? last : it;
}

std::lower_bound найдет первый интервал, который не меньше (т. Е. больше или равен), чем значение. Поскольку мы сравниваем с началом, у нас есть два случая:

  • Значение - это начало интервала, и в этом случае будет возвращен сам интервал (firstif);
  • Значение не является началом интервала, и в этом случае будет возвращен следующий интервал, поэтому нам нужно уменьшить it (--it).

Поскольку мы проверяем только начало в std::lower_bound, нам нужно проверить конец перед возвратом.

std::lower_bound имеет логарифмическую сложность, и приведенный выше вызов действителен, потому что диапазон [first, last) упорядочен по отношению к компаратору, который мы предоставляем (лямбда) - я предполагаю, что db сортируется в соответствии с началом интервалов.

См. http://rextester.com/FBHYH63411 для полной реализации.

Примечание. Если вы не часто вставляете / удаляете интервалы, возможно, вам лучше использовать отсортированный std::vector.


Изменить: старый ответ. Вероятно, вы не можете использовать std::set::find, чтобы найти свой интервал, потому что компаратор, который вы используете в db, сравнивает два interval, а не interval и int, а std::set::find использует этот компаратор (даже с " фиктивный "интервал, вам понадобится действительное отношение, которое может быть трудно получить).

Либо вам нужно изменить структуру и использовать, например, Interval Tree, чтобы сохранить логарифмическую сложность, либо вы используете неспециализированный std::find с линейной сложностью:

std::find(db.begin(), db.end(), [n](const std::unique_ptr<interval> &it) {
    return it->start < n && n < it->end;
});
person Holt    schedule 26.09.2016
comment
использование линейного алгоритма не вариант. Я пытаюсь реализовать дерево интервалов и сталкиваюсь с этой проблемой - person Hanna Khalil; 26.09.2016
comment
@HannaKhalil Если вы реализуете дерево интервалов и храните interval в дереве, у вас, вероятно, где-то есть проблема. Дерево интервалов представляет собой набор интервалов, но его узлы не являются сами интервалом, что делает возможными запросы с логарифмической сложностью. - person Holt; 26.09.2016
comment
не могли бы вы объяснить, что вы имеете в виду? Я реализую интервальные деревья, имея std::set<interval> - person Hanna Khalil; 27.09.2016
comment
@HannaKhalil Вы не можете реализовать дерево интервалов с одним std::set<interval>, потому что с std::set у вас есть отношение на interval, которое будет основано только на начале или конце (что не то, что вы хотите, за исключением случаев, когда ни один из интервалов не перекрывается). Дерево интервалов - это настраиваемая структура, в которой каждый узел представляет момент времени, а не интервал, и содержит несколько интервалов. - person Holt; 27.09.2016
comment
Мне нужны неперекрывающиеся интервалы - person Hanna Khalil; 27.09.2016
comment
@HannaKhalil Добавьте это к своему вопросу. В этом случае вы можете использовать std::set<interval> (но вы не можете называть это деревом интервалов). При этом вы, вероятно, могли бы использовать фиктивный интервальный класс с std::set::lower_bound или std::set::upper_bound. - person Holt; 27.09.2016
comment
@HannaKhalil См. Обновленный ответ с использованием std::lower_bound с настраиваемым компаратором (на самом деле фиктивный интервал не нужен). - person Holt; 27.09.2016