Почему std:: set не имеет функции "содержит"?

Я сильно использую std::set<int>, и часто мне просто нужно проверить, содержит ли такой набор номер или нет.

Я нашел бы естественным писать:

if (myset.contains(number))
   ...

Но из-за отсутствия члена contains мне нужно написать громоздкое:

if (myset.find(number) != myset.end())
  ..

или не так очевидно:

if (myset.count(element) > 0) 
  ..

Есть ли причина для этого дизайнерского решения?

Ответ 1

Я думаю, что это, вероятно, потому, что они пытались сделать std::set и std::multiset как можно более похожими. (И, очевидно, count имеет совершенно разумный смысл для std::multiset.)

Лично я думаю, что это была ошибка.

Это выглядит не так уж плохо, если вы притворяетесь, что count - это просто орфографическая ошибка contains и записать тест как:

if (myset.count(element)) 
   ...
Тем не менее, это позор.

Ответ 2

Чтобы иметь возможность писать if (s.contains()), contains() должен возвращать bool (или тип, конвертируемый в bool, что является другой историей), как это делает binary_search.

фундаментальная причина принятия решения о дизайне , а не сделать это таким образом, заключается в том, что contains(), который возвращает bool, потеряет ценную информацию о том, где находится элемент в коллекция. find() сохраняет и возвращает эту информацию в форме итератора, поэтому является лучшим выбором для универсальной библиотеки, такой как STL. Это всегда было руководящим принципом для Алекса Степанова, как он часто объяснял (например, здесь).

Что касается подхода count() в целом, хотя он часто является хорошим решением, проблема в том, что он выполняет больше работы, чем contains().

Это не означает, что bool contains() не очень хорошо иметь или даже необходимо. Некоторое время назад у нас была долгая дискуссия об этой же самой проблеме в Стандарт ISO C++ - Группа будущих предложений.

Ответ 3

Этого не хватает, потому что никто не добавил его. Никто не добавил его, потому что контейнеры из STL, в которые встроена библиотека std, где они были минимальными в интерфейсе. (Обратите внимание, что std::string не поступал из STL таким же образом).

Если вы не против какого-то странного синтаксиса, вы можете подделать его:

template<class K>
struct contains_t {
  K&& k;
  template<class C>
  friend bool operator->*( C&& c, contains_t&& ) {
    auto range = std::forward<C>(c).equal_range(std::forward<K>(k));
    return range.first != range.second;
    // faster than:
    // return std::forward<C>(c).count( std::forward<K>(k) ) != 0;
    // for multi-meows with lots of duplicates
  }
};
template<class K>
containts_t<K> contains( K&& k ) {
  return {std::forward<K>(k)};
}

использование:

if (some_set->*contains(some_element)) {
}

В принципе, вы можете писать методы расширения для большинства типов С++ std, используя эту технику.

Для этого гораздо больше смысла:

if (some_set.count(some_element)) {
}

но меня удивляет метод метода расширения.

Самое печальное, что писать эффективный contains может быть быстрее на multimap или multiset, поскольку они просто должны найти один элемент, а count должен найти каждый из них и подсчитать их.

Мультимножество, содержащее 1 миллиард экземпляров 7 (вы знаете, в случае, если вы закончите) может иметь очень медленный .count(7), но может иметь очень быстрый contains(7).

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

Ответ 4

Вы смотрите на конкретный случай и не видите большую картинку. Как указано в документации std::set отвечает требованиям AssociativeContainer концепция. Для этой концепции не имеет смысла иметь метод contains, поскольку он почти бесполезен для std::multiset и std::multimap, но count отлично работает для всех из них. Хотя метод contains может быть добавлен как псевдоним для count для std::set, std::map и их хэшированных версий (например, length для size() в std::string), но выглядит так, как создатели библиотеки не видели реальной потребности в нем.

Ответ 5

Хотя я не знаю, почему std::set не имеет contains, но count, который только возвращает 0 или 1, вы можете написать шаблонную вспомогательную функцию contains следующим образом:

template<class Container, class T>
auto contains(const Container& v, const T& x)
-> decltype(v.find(x) != v.end())
{
    return v.find(x) != v.end();
}

И используйте его следующим образом:

    if (contains(myset, element)) ...

Ответ 6

Истинная причина для set - загадка для меня, но одно возможное объяснение для этой же конструкции в map может заключаться в том, чтобы предотвратить случайное обращение людей с неэффективным кодом:

if (myMap.contains("Meaning of universe"))
{
    myMap["Meaning of universe"] = 42;
}

Это приведет к двум поисковым запросам map.

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

auto position = myMap.find("Meaning of universe");
if (position != myMap.cend())
{
    position->second = 42;
}

который потребляет только один поиск map.

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

struct Dog
{
    std::string name;
    void bark();
}

operator <(Dog left, Dog right)
{
    return left.name < right.name;
}

std::set<Dog> dogs;
...
if (dogs.contain("Husky"))
{
    dogs.find("Husky")->bark();
}

Конечно, все это просто предположение.

Ответ 7

А как насчет бинарного поиска?

 set <int> set1;
 set1.insert(10);
 set1.insert(40);
 set1.insert(30);
 if(std::binary_search(set1.begin(),set1.end(),30))
     bool found=true;

Ответ 8

Другая причина заключается в том, что это даст программисту ложное впечатление, что std:: set - это набор в смысле теории математического набора. Если они реализуют это, тогда последует много других вопросов: если std:: set содержит() для значения, почему он не имеет его для другого набора? Где union(), intersection() и другие заданные операции и предикаты?

Разумеется, ответ состоит в том, что некоторые из заданных операций уже реализованы как функции в (std:: set_union() и т.д.), а другие реализованы так же тривиально, как contains(). Функции и объекты функций лучше работают с математическими абстракциями, чем элементы объекта, и они не ограничены конкретным типом контейнера.

Если нужно реализовать полную функциональность с математическим набором, у него есть не только выбор базового контейнера, но также он имеет выбор деталей реализации, например, будет ли его функция theory_union() работать с неизменяемыми объектами, лучше подходит для функционального программирования, или он изменит свои операнды и сохранит память? Будет ли он реализован как объект функции с самого начала или лучше реализовать C-функцию и использовать std:: function < > если необходимо?

Как и сейчас, std:: set - это просто контейнер, хорошо подходящий для реализации набора в математическом смысле, но он почти не является теоретическим набором, как std::vector, из теоретического вектора.