¿Por qué std:: set no tiene una función miembro "contains"?


Estoy usando mucho std::set<int> y a menudo simplemente necesito verificar si un conjunto de este tipo contiene un número o no.

Me parece natural escribir:

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

Pero debido a la falta de un contains miembro, necesito escribir lo engorroso:

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

O lo no tan obvio:

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

¿Hay alguna razón para esta decisión de diseño ?

Author: Jabberwocky, 2017-03-01

7 answers

Creo que fue probablemente porque estaban tratando de hacer std::set y std::multiset lo más similar posible. (Y obviamente count tiene un significado perfectamente sensible para std::multiset.)

Personalmente creo que esto fue un error.

No se ve tan mal si finges que count es solo un error ortográfico de contains y escribes la prueba como:

if (myset.count(element)) 
   ...

Sigue siendo una lástima.

 142
Author: Martin Bonner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-01 13:14:41

Para poder escribir if (s.contains()), contains() tiene que devolver un bool (o un tipo convertible a bool, que es otra historia), como lo hace binary_search.

La razón fundamental detrás de la decisión de diseño no para hacerlo de esta manera es que contains() que devuelve un bool perdería información valiosa sobre dónde está el elemento en la colección. find() conserva y devuelve esa información en forma de iterador, por lo tanto es una mejor opción para una biblioteca genérica como STL. Este siempre ha sido el principio guía para Alex Stepanov, como ha explicado a menudo (por ejemplo, aquí).

En cuanto al enfoque count() en general, aunque a menudo es una solución aceptable, el problema con él es que hace más trabajo que un contains() tendría que hacer.

Eso no quiere decir que un bool contains() no sea muy agradable de tener o incluso necesario. Hace un tiempo tuvimos unalarga discusión sobre este mismo tema en el ISO C++ Standard-Future Proposals group.

 35
Author: Leo Heinsaar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-17 05:47:26

Le falta porque nadie lo agregó. Nadie lo agregó porque los contenedores del STL que la biblioteca std incorporó estaban diseñados para ser mínimos en la interfaz. (Nótese que std::string no vino del STL de la misma manera).

Si no te importa alguna sintaxis extraña, puedes fingirla: {[15]]}

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)};
}

Uso:

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

Básicamente, puede escribir métodos de extensión para la mayoría de los tipos de C++ std utilizando esta técnica.

Tiene mucho más sentido simplemente hacer esto:

if (some_set.count(some_element)) {
}

Pero me divierte el método del método de extensión.

Lo realmente triste es que escribir un eficiente contains podría ser más rápido en un multimap o multiset, ya que solo tienen que encontrar un elemento, mientras que count tiene que encontrar cada uno de ellos y contarlos.

Un multiset que contiene 1 mil millones de copias de 7 (ya sabes, en caso de que se agote) puede tener un .count(7) realmente lento, pero podría tener un contains(7) muy rápido.

Con el método de extensión anterior, podríamos hacer es más rápido para este caso usando lower_bound, comparando con end, y luego comparando con el elemento. Hacer eso para un maullido desordenado, así como un maullido ordenado requeriría SFINAE de lujo o sobrecargas específicas del contenedor, sin embargo.

 21
Author: Yakk - Adam Nevraumont,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-02 13:33:57

Usted está buscando en un caso particular y no ver una imagen más grande. Como se indica en la documentación std::set cumple con el requisito del concepto AssociativeContainer . Para que el concepto no tiene ningún sentido tener contains método, ya que es bastante inútil para std::multiset y std::multimap pero count, funciona bien para todos ellos. Aunque el método contains podría agregarse como un alias para count para std::set, std::map y sus versiones hash (como length para size() en std::string), pero parece biblioteca los creadores no vieron la necesidad real de ello.

 12
Author: Slava,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-01 15:24:10

Aunque no se por qué std::set no tiene contains pero count que solo vuelve 0 o 1, puedes escribir una función auxiliar con plantilla contains como esta:

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();
}

Y úsalo así:

    if (contains(myset, element)) ...
 10
Author: rustyx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-01 15:13:04

La verdadera razón de set es un misterio para mí, pero una posible explicación para este mismo diseño en map podría ser evitar que la gente escriba código ineficiente por accidente:

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

Que daría lugar a dos búsquedas map.

En su lugar, se ve obligado a obtener un iterador. Esto le da una pista mental de que debe reutilizar el iterador:

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

Que consume solo una map búsqueda.

Cuando nos damos cuenta de que set y map están hechos de la misma carne, podemos aplicar este principio también a set. Es decir, si queremos actuar sobre un elemento en el set sólo si está presente en el set, este diseño pueden evitar escribir código como este:

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();
}

Por supuesto, todo esto es una mera especulación.

 7
Author: Martin Drozdik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-01 18:13:53

Otra razón es que le daría a un programador la falsa impresión de que std::set es un conjunto en el sentido de la teoría de conjuntos de matemáticas. Si implementan eso, entonces muchas otras preguntas seguirían: si un std::set tiene contains() para un valor, ¿por qué no lo tiene para otro conjunto? ¿Dónde están union (), intersection () y otras operaciones y predicados de conjuntos?

La respuesta es, por supuesto, que algunas de las operaciones de set ya están implementadas como funciones en (std::set_union() etc.) y otros son tan trivialmente implementados como contains(). Las funciones y los objetos de función funcionan mejor con abstracciones matemáticas que los miembros de objetos, y no se limitan al tipo de contenedor en particular.

Si uno necesita implementar una funcionalidad completa de conjunto matemático, no solo tiene una opción de contenedor subyacente, sino también tiene una opción de detalles de implementación, por ejemplo, su función theory_union() funcionaría con objetos inmutables, más adecuados para la programación funcional, o modificaría su función operandos y guardar memoria? ¿Se implementaría como objeto de función desde el principio o sería mejor implementar una función C, y usar std::function si es necesario?

Como es ahora, std::set es solo un contenedor, muy adecuado para la implementación de set en el sentido matemático, pero está casi tan lejos de ser un conjunto teórico como std::vector de ser un vector teórico.

 -1
Author: Mike Tyukanov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-03 08:41:49