ejemplo - set c++ example
¿Por qué std:: set no tiene una función miembro "contiene"? (8)
Estoy usando
std::set<int>
y, a menudo, simplemente necesito verificar si dicho conjunto contiene un número o no.
Me resultaría natural escribir:
if (myset.contains(number))
...
Pero debido a la falta de un miembro
contains
, necesito escribir el engorroso:
if (myset.find(number) != myset.end())
..
o el no tan obvio:
if (myset.count(element) > 0)
..
¿Hay alguna razón para esta decisión de diseño?
¿Qué pasa con binary_search?
set <int> set1;
set1.insert(10);
set1.insert(40);
set1.insert(30);
if(std::binary_search(set1.begin(),set1.end(),30))
bool found=true;
Aunque no sé por qué
std::set
no
contains
pero
count
que solo devuelve
0
o
1
, puede escribir una función auxiliar de plantilla 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)) ...
Creo que probablemente fue porque estaban tratando de hacer
std::set
y
std::multiset
más similar posible.
(Y obviamente,
count
tiene un significado perfectamente sensato para
std::multiset
).
Personalmente creo que esto fue un error.
No se ve tan mal si finges que el
count
es solo una falta de ortografía de
contains
y escribe la prueba como:
if (myset.count(element))
...
Sin embargo, sigue siendo una pena.
Estás buscando un caso particular y no ves una imagen más grande.
Como se indica en la
documentation
std::set
cumple con los requisitos del concepto
AssociativeContainer
.
Para ese concepto, no tiene ningún sentido tener un método
contains
, 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 agregar como un alias para el
count
de
std::set
,
std::map
y sus versiones hash (como
length
for
size()
en
std::string
), pero parece que los creadores de la biblioteca no vieron la necesidad real de eso.
La verdadera razón para
set
es un misterio para mí, pero una posible explicación para este mismo diseño en el
map
podría ser evitar que las personas escriban código ineficiente por accidente:
if (myMap.contains("Meaning of universe"))
{
myMap["Meaning of universe"] = 42;
}
Lo que resultaría en dos búsquedas de
map
.
En cambio, 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 búsqueda en el
map
.
Cuando nos damos cuenta de que el
set
y el
map
están hechos de la misma carne, podemos aplicar este principio también al
set
.
Es decir, si queremos actuar sobre un elemento del
set
solo si está presente en el
set
, este diseño puede evitar que escribamos 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.
Le falta porque nadie lo agregó.
Nadie lo agregó porque los contenedores del STL que incorporó la biblioteca estándar estaban diseñados para tener una interfaz mínima.
(Tenga en cuenta que
std::string
no provenía del STL de la misma manera).
Si no le importa alguna sintaxis extraña, puede simularla:
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)};
}
utilizar:
if (some_set->*contains(some_element)) {
}
Básicamente, puede escribir métodos de extensión para la mayoría de los tipos estándar de C ++ utilizando esta técnica.
Tiene mucho más sentido 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 contenido eficiente podría ser más rápido en un
multimap
o en un conjunto
multimap
, ya que solo tienen que encontrar un elemento, mientras que
count
tiene que encontrar cada uno de ellos
y contarlos
.
Un conjunto múltiple que contiene mil millones de copias de 7 (ya sabe, en caso de que se agote) puede tener un
.count(7)
muy lento
.count(7)
, pero podría tener un contenido muy rápido
contains(7)
.
Con el método de extensión anterior, podríamos hacerlo más rápido para este caso usando
lower_bound
, comparando hasta el
end
y luego comparando con el elemento.
Sin embargo, hacer eso para un maullido desordenado, así como para un maullido ordenado, requeriría SFINAE elegante o sobrecargas específicas del contenedor.
Otra razón es que le daría al programador la falsa impresión de que std :: set es un conjunto en el sentido de la teoría de conjuntos matemáticos. Si implementan eso, entonces seguirían muchas otras preguntas: si un std :: set tiene contiene () para un valor, ¿por qué no lo tiene para otro conjunto? ¿Dónde están union (), intersection () y otras operaciones de conjunto y predicados?
La respuesta es, por supuesto, que algunas de las operaciones de conjunto ya están implementadas como funciones en (std :: set_union () etc.) y otras están implementadas tan trivialmente como contiene (). Las funciones y los objetos de función funcionan mejor con las abstracciones matemáticas que los miembros del objeto, y no se limitan al tipo de contenedor particular.
Si uno necesita implementar una funcionalidad completa de conjunto matemático, no solo tiene una opción de contenedor subyacente, sino que también tiene una opción de detalles de implementación, por ejemplo, su función teoría_unión () funcionaría con objetos inmutables, más adecuados para la programación funcional o modificaría sus operandos y ahorraría 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 fuera necesario?
Tal como está ahora, std :: set es solo un contenedor, adecuado para la implementación de set en sentido matemático, pero está casi tan lejos de ser un conjunto teórico como std :: vector de ser un vector teórico.
Para poder escribir
if (s.contains())
,
contains()
tiene que devolver un
bool
(o un tipo convertible a
bool
, que es otra historia), como
binary_search
hace
binary_search
.
La
razón fundamental
detrás de la decisión de diseño de
no
hacerlo de esta manera es que
contains()
que devuelve un valor
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 rector de Alex Stepanov, como ha explicado a menudo (por ejemplo,
here
).
En cuanto al enfoque
count()
en general, aunque a menudo es una solución alternativa aceptable, el problema es que
hace más trabajo del que
tendría que hacer
un
contains()
.
Eso no quiere decir que un
bool contains()
no es muy agradable o incluso necesario.
Hace un
tiempo
tuvimos una
larga discusión
sobre este mismo problema en el grupo ISO C ++ Standard - Future Proposals.