Как проверить, что элемент находится в std :: set?

330

Как вы проверяете, что элемент находится в наборе?

Есть ли более простой эквивалент следующего кода:

myset.find(x) != myset.end()
fulmicoton
источник
4
Единственный способ сделать это проще - это логический предикат: template <typename T> bool member (T const & item). И это будет реализовано (под прикрытием) с точки зрения линии, о которой вы спрашиваете.
Дон Уэйкфилд

Ответы:

400

Типичный способ проверить существование во многих STL контейнеров , таких как std::map, std::set... это:

const bool is_in = container.find(element) != container.end();
размотать
источник
25
это специфично для наборов и карт. векторы, списки и т. д. не имеют функции поиска.
Вильгельмтелл
8
IMO, использующий count (), лучше, потому что он просто короче и конвертируется в bool, как отмечено в ответе Питера. Я не понимаю, почему этот ответ был принят и так много очков ...
Огњен Шобајић
4
Для полноты: векторы / списки могут использовать std :: find std::find(container.begin(), container.end(), element) != container.end():; O (N) проблема остается, конечно ...
Аконкагуа
10
@MichaelMathews В вашем варианте: сначала if(container.find(foo) == container.end())нужно выполнить поиск по дереву, чтобы найти элемент - если он не найден, то нужно выполнить поиск по второму дереву, чтобы найти правильное место вставки. У оригинального варианта if(container.insert(foo).second) {...}есть очарование, что ему нужен только один поиск дерева ...
Аконкагуа
23
есть , set.contains(x)что возвращает BOOL в ++ 20 стандарта C. Я не знаю, почему это заняло у нас до 2020 года.
Грэмвелл
215

Другой способ просто сказать, существует ли элемент, это проверить count()

if (myset.count(x)) {
   // x is in the set, count is 1
} else {
   // count zero, i.e. x not in the set
}

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

Так что я все равно должен найти итератор. Тогда, конечно, лучше просто сравнить с этим end.

set< X >::iterator it = myset.find(x);
if (it != myset.end()) {
   // do something with *it
}

С ++ 20

В C ++ 20 set получает containsфункцию, поэтому становится возможным следующее, как упомянуто по адресу: https://stackoverflow.com/a/54197839/895245

if (myset.contains(x)) {
  // x is in the set
} else {
  // no x 
}
Pieter
источник
102
Обратите внимание, что использование count()вместо find()никогда не лучше, но потенциально хуже. Это потому, find()что вернется после первого совпадения, count()всегда будет перебирать все элементы.
Фрерих Раабе
34
@Frerich, это только актуально, multisetи multimapя подумал? Тем не менее, приятно отметить, что :)
Питер
83
std :: set обычно реализуется с упорядоченной древовидной структурой, поэтому count () и find () будут иметь O (logn). Ни один из них не будет перебирать все элементы в наборе.
Алан
14
@FrerichRaabe - Вы уверены? Поскольку возможно setсодержать только один совпадающий член, разве функция не будет реализована таким образом, чтобы останавливаться после нахождения первого элемента, в данном случае, как указывает Питер? Полезный ответ в любом случае!
Дан Ниссенбаум
14
@DanNissenbaum Да, вы совершенно правы (как и Питер + и Алан): для std :: set две функции эквивалентны с точки зрения производительности. Поэтому, хотя первая часть моего комментария ( count()никогда не быстрая, чем find()) по-прежнему имеет место, вторая часть действительно не применима к std::set. Тем не менее, я предполагаю, что можно привести еще один аргумент в пользу find(): он более выразителен, то есть подчеркивает, что вы пытаетесь найти элемент вместо подсчета количества вхождений.
Фрерих Раабе
42

Просто чтобы прояснить, причина, по которой contains()в этих типах контейнеров нет элементов, подобных тем, что это откроет вам возможность писать неэффективный код. Такой метод, вероятно, будет просто this->find(key) != this->end()внутренним, но подумайте, что вы делаете, когда ключ действительно присутствует; в большинстве случаев вы захотите получить элемент и что-то с ним сделать. Это означает, что вам нужно сделать секунду find(), что неэффективно. Лучше использовать поиск напрямую, чтобы вы могли кэшировать свой результат, например так:

auto it = myContainer.find(key);
if (it != myContainer.end())
{
    // Do something with it, no more lookup needed.
}
else
{
    // Key was not present.
}

Конечно, если вы не заботитесь об эффективности, вы всегда можете свернуть свои собственные, но в этом случае вам, вероятно, не следует использовать C ++ ...;)

Тим
источник
44
Как насчет наборов? Обычно у вас уже есть элемент, но вы просто хотите проверить, присутствует ли он.
Элазар Лейбович
8
Есть ли у вас какие-либо ссылки на то, является ли это действительной причиной, по которой такой метод / функция не включен в stl, или это просто ваше обоснованное предположение?
Фабио А.
3
@FabioA. Это мое обоснованное предположение.
Тим
1
@ Adhemar, последовательность не совсем сильная сторона STL ... ( list::remove, remove(makes_sense_only_for_vector, iterators)...)
Элазар Лейбович
3
Мне не имеет смысла не включать функцию, потому что кто-то может использовать ее неправильно, если он не знает, что делает. Программирование предназначено для людей, которые могут думать сами за себя и нести ответственность за свой код и его производительность
slawekwin
13

В C ++ 20 мы наконец получим std::set::containsметод.

#include <iostream>
#include <string>
#include <set>

int main()
{
    std::set<std::string> example = {"Do", "not", "panic", "!!!"};

    if(example.contains("panic")) {
        std::cout << "Found\n";
    } else {
        std::cout << "Not found\n";
    }
}
Денис Саблуков
источник
6

Если вы собираетесь добавить containsфункцию, она может выглядеть так:

#include <algorithm>
#include <iterator>

template<class TInputIterator, class T> inline
bool contains(TInputIterator first, TInputIterator last, const T& value)
{
    return std::find(first, last, value) != last;
}

template<class TContainer, class T> inline
bool contains(const TContainer& container, const T& value)
{
    // This works with more containers but requires std::begin and std::end
    // from C++0x, which you can get either:
    //  1. By using a C++0x compiler or
    //  2. Including the utility functions below.
    return contains(std::begin(container), std::end(container), value);

    // This works pre-C++0x (and without the utility functions below, but doesn't
    // work for fixed-length arrays.
    //return contains(container.begin(), container.end(), value);
}

template<class T> inline
bool contains(const std::set<T>& container, const T& value)
{
    return container.find(value) != container.end();
}

Это работает с std::setдругими контейнерами STL и даже массивами фиксированной длины:

void test()
{
    std::set<int> set;
    set.insert(1);
    set.insert(4);
    assert(!contains(set, 3));

    int set2[] = { 1, 2, 3 };
    assert(contains(set2, 3));
}

Редактировать:

Как указано в комментариях, я непреднамеренно использовал функцию, новую для C ++ 0x ( std::beginи std::end). Вот почти тривиальная реализация VS2010:

namespace std {

template<class _Container> inline
    typename _Container::iterator begin(_Container& _Cont)
    { // get beginning of sequence
    return (_Cont.begin());
    }

template<class _Container> inline
    typename _Container::const_iterator begin(const _Container& _Cont)
    { // get beginning of sequence
    return (_Cont.begin());
    }

template<class _Container> inline
    typename _Container::iterator end(_Container& _Cont)
    { // get end of sequence
    return (_Cont.end());
    }

template<class _Container> inline
    typename _Container::const_iterator end(const _Container& _Cont)
    { // get end of sequence
    return (_Cont.end());
    }

template<class _Ty,
    size_t _Size> inline
    _Ty *begin(_Ty (&_Array)[_Size])
    { // get beginning of array
    return (&_Array[0]);
    }

template<class _Ty,
    size_t _Size> inline
    _Ty *end(_Ty (&_Array)[_Size])
    { // get end of array
    return (&_Array[0] + _Size);
    }

}
Сэм Харвелл
источник
1
@ Adhemar, на самом деле это было неэффективно, но совсем не по той причине, которую вы упомянули.
Сэм Харвелл
@ Пол: Убедитесь, что вы включили специализацию для std::set, и помните, что это уместно, только если единственное, что вам нужно знать, это существование.
Сэм Харвелл
@ 280Z28: std :: begin (контейнер)? Что это за стандарт STL? Это не компилируется на моем GCC.
stefaanv
@stefannv: хех, это новое для C ++ 0x. Я добавил реализацию из моего компилятора выше.
Сэм Харвелл
2
@ Adhemar: Если вы знаете, что набор содержит значение, то вы уже это значение. Единственная причина, по которой вам нужен итератор - это удаление элемента из набора. Если все, что вам нужно, это знать, содержит ли коллекция значение, то это решение не менее эффективно, чем любое другое решение.
Сэм Харвелл
4

Вы также можете проверить, находится ли элемент в наборе или нет при вставке элемента. Версия с одним элементом возвращает пару, в которой для ее элемента pair :: first установлено значение итератора, указывающее либо на вновь вставленный элемент, либо на эквивалентный элемент, уже находящийся в наборе. Элемент pair :: second в паре устанавливается равным true, если новый элемент был вставлен, или false, если эквивалентный элемент уже существует.

Например: предположим, что в наборе уже есть 20 в качестве элемента.

 std::set<int> myset;
 std::set<int>::iterator it;
 std::pair<std::set<int>::iterator,bool> ret;

 ret=myset.insert(20);
 if(ret.second==false)
 {
     //do nothing

 }
 else
 {
    //do something
 }

 it=ret.first //points to element 20 already in set.

Если элемент недавно вставлен, то pair :: first будет указывать на позицию нового элемента в наборе.

Прашант Шубхам
источник
2

Напишите свой собственный:

template<class T>
bool checkElementIsInSet(const T& elem, const std::set<T>& container)
{
  return container.find(elem) != container.end();
}
stefaanv
источник
4
только что сделал: template <class T> static inline bool содержит (const std :: set <T> & S, T x) {return (S.find (x)! = S.end ()); }
fulmicoton
4
@paul не создает статические глобальные функции. вместо этого поместите вашу функцию в анонимное пространство имен: это C ++ способ создания функций, которые не будут ссылаться на другие модули компиляции. Кроме того, ваш параметр T должен быть константным эталоном, для правильности констант и для эффективности.
Вильгельмтелл
-1: Не шаблонный и не совсем в стиле STL. Это нормально, если вы не используете STL, но если вы используете STL, вы должны, по крайней мере, попытаться следовать его стандартам.
Сэм Харвелл
1
@ 280Z28: Мне жаль, что мой код не соответствует вашим стандартам, я просто показал, что если вам не нравится интерфейс STL, вы можете написать свой собственный. Боже, не шаблон? Насколько шаблонным это должно быть? Ваш пример выглядит хорошо, это не значит, что у меня плохо. Это просто больше сосредоточено на наборе, как было задано ОП.
stefaanv
1
@ 280Z28: я просто делал выводы. Я думал, что люди будут достаточно умны, чтобы получить картину.
stefaanv
2

я использую

if(!my_set.count(that_element)) //Element is present...
;

Но это не так эффективно, как

if(my_set.find(that_element)!=my_set.end()) ....;

Моя версия только экономит мое время при написании кода. Я предпочитаю это так для конкурентного кодирования.

Манас Бондейл
источник
Да, count(). Любой, кто не в состоянии понять, что функция, возвращающая целое число, используемая в булевом выражении, проверяет ненулевое, будет иметь много, много других проблем в большом море идиом C / C ++. И, как отмечалось выше, действительно должно быть максимально эффективно для наборов, о чем и шла речь.
Рон Берк
0

Я был в состоянии написать общую containsфункцию для std::listи std::vector,

template<typename T>
bool contains( const list<T>& container, const T& elt )
{
  return find( container.begin(), container.end(), elt ) != container.end() ;
}

template<typename T>
bool contains( const vector<T>& container, const T& elt )
{
  return find( container.begin(), container.end(), elt ) != container.end() ;
}

// use:
if( contains( yourList, itemInList ) ) // then do something

Это немного очищает синтаксис.

Но я не мог использовать магию параметра шаблона шаблона, чтобы заставить эту работу работать с произвольными stl-контейнерами.

// NOT WORKING:
template<template<class> class STLContainer, class T>
bool contains( STLContainer<T> container, T elt )
{
  return find( container.begin(), container.end(), elt ) != container.end() ;
}

Любые комментарии об улучшении последнего ответа были бы хорошими.

bobobobo
источник
Извините, я не могу написать блочный код в комментариях, но как насчет template<typename CONTAINER, typename CONTAINEE> bool contains(const CONTAINER& container, const CONTAINEE& needle) { return find(container.begin(), container.end(), needle) != container.end();
fulmicoton
Это не работает, потому что std :: vector нуждается в дополнительном распределителе в качестве аргумента шаблона, а std :: set требует распределителя и меньшего аргумента шаблона. Эти строки woulkd работают: template <template <class, class> class STLContainer, class T, class A> bool содержит (STLContainer <T, A> контейнер, T elt) {return find (container.begin (), container.end ( ), elt)! = container.end (); } template <template <класс, класс, класс> класс STLContainer, класс T, класс L, класс A> bool содержит (контейнер STLContainer <T, A, L>, T elt) {return find (container.begin (), контейнер .end (), elt)! = container.end (); }
tgmath
0

// общий синтаксис

       set<int>::iterator ii = find(set1.begin(),set1.end(),"element to be searched");

/ * в приведенном ниже коде я пытаюсь найти элемент 4 и установить int, если он присутствует или нет * /

set<int>::iterator ii = find(set1.begin(),set1.end(),4);
 if(ii!=set1.end())
 {
    cout<<"element found";
    set1.erase(ii);// in case you want to erase that element from set.
 }
Санджив
источник