Perché std :: set non ha una funzione membro "contiene"?


103

Sto usando pesantemente std::set<int> e spesso ho semplicemente bisogno di controllare se un tale set contiene un numero o meno.

Troverei naturale scrivere:

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

Ma a causa della mancanza di un containsmembro, ho bisogno di scrivere l'ingombrante:

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

o il non così ovvio:

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

C'è una ragione per questa decisione progettuale?


7
La maggior parte della libreria standard funziona con gli iteratori, quindi normalmente le funzioni che restituiscono iteratori sono ciò che ti aspetteresti. Non è difficile però scrivere una funzione per astrarla. Molto probabilmente il compilatore lo inserirà in linea poiché dovrebbe essere solo una riga o 2 di codice e otterrai le stesse prestazioni.
NathanOliver

3
Un altro problema (più fondamentale) con l' count()approccio è che fa più lavoro di quanto countains()dovrebbe fare a.
Leo Heinsaar

11
La ragione fondamentale dietro questa decisione progettuale è quella contains()che restituisce una boolsarebbe perdere preziose informazioni su dove l'elemento è nella collezione . find()conserva e restituisce tali informazioni sotto forma di un iteratore, quindi è una scelta migliore per una libreria generica come STL. (Questo non vuol dire che un bool contains()non sia molto carino o addirittura necessario, però.)
Leo Heinsaar

3
È facile scrivere una contains(set, element)funzione gratuita utilizzando l'interfaccia pubblica del set. Pertanto, l'interfaccia del set è funzionalmente completa; l'aggiunta di un metodo pratico aumenta semplicemente l'interfaccia senza abilitare alcuna funzione aggiuntiva, il che non è il modo C ++.
Toby Speight

3
Stiamo chiudendo tutto in questi giorni? In che modo questa domanda è "basata principalmente sull'opinione"?
Mr. Alien

Risposte:


148

Penso che probabilmente era perché stavano cercando di creare std::sete il std::multisetpiù simile possibile. (E ovviamente countha un significato perfettamente sensato per std::multiset.)

Personalmente penso che sia stato un errore.

Non sembra poi così male se fingi che countsia solo un errore di ortografia containse scrivi il test come:

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

È comunque un peccato.


5
Per inciso, è esattamente lo stesso con mappe e multimappa (che è altrettanto brutto, ma meno brutto di tutti questi confronti con .end()).
Matteo Italia

8
In alternativa, potrebbero non aver visto la necessità di un membro aggiuntivo contains(), sulla base del fatto che sarebbe ridondante perché per qualsiasi std::set<T> se T t, il risultato di s.contains(t)è esattamente identico al risultato di static_cast<bool>(s.count(t)). Poiché l'utilizzo del valore in un'espressione condizionale ne verrebbe implicitamente castato abool , potrebbero aver ritenuto che count()servisse allo scopo abbastanza bene.
Justin Time - Ripristina Monica

2
Errore di ortografia? if (myset.ICanHaz(element)) ...: D
Stéphane Gourichon

3
@MartinBonner Non importa se le ragioni per lasciarlo fuori erano stupide. Inoltre, non importa se la conversazione non fosse la logica finale al 100%. La tua risposta qui è solo un'ipotesi ragionevole su come pensi che dovrebbe essere. La conversazione e la risposta di qualcuno non solo coinvolto in essa, ma incaricato di proporla (anche se non l'hanno fatto) è indiscutibilmente più vicina alla verità di questa ipotesi, non importa come la guardi. Come minimo dovresti almeno menzionarlo in questa risposta, sarebbe un grande miglioramento e sarebbe la cosa responsabile da fare.
Jason C

2
@ JasonC: potresti andare avanti e modificare una sezione in fondo per favore? Non capisco davvero il punto che stai cercando di fare, ei commenti probabilmente non sono il modo migliore per chiarirlo. Grazie!
Martin Bonner sostiene Monica

44

Per poter scrivere if (s.contains()), contains()deve restituire un bool(o un tipo convertibile in bool, che è un'altra storia), come binary_searchfa.

La ragione fondamentale alla base della decisione progettuale non farlo in questo modo è quello contains()che restituisce un booldesiderio perdere preziose informazioni su dove l'elemento è nella collezione . find()conserva e restituisce tali informazioni sotto forma di un iteratore, quindi è una scelta migliore per una libreria generica come STL. Questo è sempre stato il principio guida di Alex Stepanov, come ha spesso spiegato (ad esempio, qui ).

Per quanto riguarda l' count()approccio in generale, sebbene sia spesso una soluzione alternativa, il problema è che funziona più di un contains() dovrebbe fare a .

Questo non vuol dire che a bool contains()non sia molto bello o addirittura necessario. Qualche tempo fa abbiamo avuto un file discusso a lungo su questo stesso problema nel gruppo ISO C ++ Standard - Future Proposals.


5
Ed è interessante notare che quella discussione si è conclusa con un quasi consenso sul fatto che fosse desiderabile e ti è stato chiesto di scrivere una proposta in merito.
PJTraill

@PJTraill True, e il motivo per cui non sono andato avanti è che contains(), ovviamente, interagirebbe fortemente con i contenitori e gli algoritmi esistenti, che sarebbero fortemente influenzati da concetti e intervalli - al momento previsto in C ++ 17 - e Ero convinto (a seguito della discussione e di un paio di scambi di e-mail privati) che fosse un'idea migliore aspettarli prima. Ovviamente, nel 2015 non era chiaro che né i concetti né gli intervalli non sarebbero entrati in C ++ 17 (in effetti, c'erano grandi speranze che lo facessero). Non sono sicuro che valga la pena perseguirlo ora, però.
Leo Heinsaar

1
Perché std::set(che è ciò su cui si pone la domanda), non vedo come countfunzioni più di quanto containsdovrebbe fare. L'implementazione glibc dicount is (approssimativamente) return find(value) == end() ? 0 : 1;. A parte i dettagli sull'operatore ternario rispetto al solo ritorno != end()(che mi aspetterei che l'ottimizzatore rimuova), non riesco a vedere come ci sia altro lavoro.
Martin Bonner supporta Monica

4
"... contains () che restituisce un bool perderebbe preziose informazioni su dove si trova l'elemento nella raccolta " - Se l'utente chiamamyset.contains() (se esistesse), sarebbe un'indicazione piuttosto forte che quell'informazione non è preziosa ( all'utente in quel contesto).
Keith Thompson

1
Perché fa count()più lavoro di quanto contains()dovrebbe fare std::set? È unico, quindi count()potrebbe essere return contains(x) ? 1 : 0;esattamente lo stesso.
Timmmm

22

Manca perché nessuno l'ha aggiunto. Nessuno l'ha aggiunto perché i contenitori dall'STL che la stdlibreria ha incorporato erano progettati per essere minimi nell'interfaccia. (Nota che std::stringnon è venuto dal STL allo stesso modo).

Se non ti dispiace una sintassi strana, puoi fingere:

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

Fondamentalmente, puoi scrivere metodi di estensione per la maggior parte dei stdtipi C ++ usando questa tecnica.

Ha molto più senso fare solo questo:

if (some_set.count(some_element)) {
}

ma sono divertito dal metodo del metodo di estensione.

La cosa veramente triste è che scrivere un efficiente containspotrebbe essere più veloce su un multimapomultiset , dato che devono solo trovare un elemento, mentre countdevono trovarli ciascuno e contarli .

Un multiset contenente 1 miliardo di copie di 7 (sai, in caso di esaurimento) può avere un tempo molto lento .count(7), ma potrebbe avere uncontains(7) .

Con il metodo di estensione sopra, potremmo renderlo più veloce per questo caso utilizzando lower_bound, confrontando ende quindi confrontando con l'elemento. Farlo per un miagolio non ordinato così come un miagolio ordinato richiederebbe comunque SFINAE fantasiosi o sovraccarichi specifici del contenitore.


2
1 miliardo di copie di 7? E qui ho pensato che std::setnon possa contenere duplicati e quindi std::set::counttornerò sempre 0o 1.
nwp

5
@nwp std::multiset::countcan
milleniumbug

2
@nwp La mia mancanza backticksintorno alla parola "set" è perché non mi riferisco in modo std::setspecifico. Per farti sentire meglio, aggiungerò multi
Yakk - Adam Nevraumont

3
Mi sembra che mi manchi la battuta per qualunque cosa "miagolio" avrebbe dovuto essere un riferimento.
user2357112 supporta Monica

2
@ user2357112 meow è un segnaposto per "set or map". Chiedi al STL perché.
Yakk - Adam Nevraumont

12

Stai esaminando un caso particolare e non vedi un'immagine più grande. Come indicato nella documentazione, std::set soddisfa i requisiti del concetto di AssociativeContainer . Per quel concetto non ha alcun senso avere un containsmetodo, poiché è praticamente inutile per std::multisete std::multimap, ma countfunziona bene per tutti loro. Anche se il metodo containspotrebbe essere aggiunto come alias per countper std::set, std::mape le loro versioni hash (come lengthper size()a std::string), ma sembra che i creatori di libreria non ha visto vero e proprio bisogno.


8
Nota che stringè un mostro: esisteva prima dell'STL, dove aveva lengthe tutti quei metodi che sono basati su indice, e poi è stato "containerizzato" per adattarsi al modello STL ... senza rimuovere i metodi esistenti per motivi di compatibilità all'indietro . Vedere GotW # 84: Monoliths Unstrung => stringviola davvero il principio di progettazione della "quantità minima di funzioni membro".
Matthieu M.

5
Ma poi la domanda diventa: "Perché vale la pena avere un concetto di AssociativeContainer come quello?" - e non sono sicuro che sia stato col senno di poi.
Martin Bonner sostiene Monica

24
Chiedere se un multiset, una multimappa o una mappa contiene qualcosa ha perfettamente senso per me. In effetti, containsè uguale nello sforzo su un set / mappa, ma può essere reso più veloce che countsu un multiset / multimappa.
Yakk - Adam Nevraumont

5
AssociativeContainer non richiede che le classi non abbiano un containsmetodo.
user2357112 supporta Monica

6
@Slava È come dire size()e empty()sono duplicati, eppure molti contenitori hanno entrambi.
Barry

10

Anche se non so perché std::setnon ha containsma countche restituisce sempre 0o 1, puoi scrivere una containsfunzione di supporto basata su modelli come questa:

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

E usalo in questo modo:

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

3
-1, perché questo è chiaramente contraddetto dal fatto che in realtà il containsmetodo esiste, è solo chiamato in modo stupido.
Matteo Italia

4
"L'STL si sforza di offrire un'interfaccia minima" gracchio di std::string tosse
bolov

6
@bolov: il tuo punto? std.::stringNON fa parte del STL! Fa parte della libreria standard ed è stato modellato retroattivamente ...
MFH

3
@MatteoItalia countpuò essere più lento poiché ha effettivamente bisogno di fare due finds per ottenere l'inizio e la fine dell'intervallo se il codice è condiviso con multiset.
Mark Ransom

2
L'OP sa già che è ridondante, ma a quanto pare vuole che il codice venga letto esplicitamente contains. Non vedo niente di sbagliato in questo. @MarkRansom il piccolo SFINAE deve impedire a questo modello di legarsi a cose che non dovrebbe.
rustyx

7

La vera ragione setè un mistero per me, ma una possibile spiegazione per questo stesso progetto mappotrebbe essere quella di impedire alle persone di scrivere codice inefficiente per sbaglio:

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

Il che comporterebbe due mapricerche.

Invece, sei costretto a ottenere un iteratore. Questo ti dà un suggerimento mentale che dovresti riutilizzare l'iteratore:

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

che consuma solo una mapricerca.

Quando ce ne rendiamo conto sete mapsiamo fatti della stessa carne, possiamo applicare questo principio anche a set. Cioè, se vogliamo agire su un elemento nel setsolo se è presente nel set, questo design può impedirci di scrivere codice come questo:

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

Ovviamente tutto questo è solo una speculazione.


1
Sì, ma per i set di int questo non si applica.
Jabberwocky

7
Solo che le persone sanno scrivere if (myMap.count("Meaning of universe"))bene, quindi ...?
Barry

@MichaelWalz Oops, hai ragione. Ho modificato la mia risposta per includere anche l'esempio impostato. Tuttavia, il ragionamento per una serie di int è un mistero per me.
Martin Drozdik

2
Non può essere vero. Possono scrivere il tuo codice inefficiente con la stessa facilità con containscui con count.
Martin Bonner sostiene Monica

2

Dal c ++ 20,

bool contains( const Key& key ) const

è disponibile.


0

E per quanto riguarda 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;

Non funzionerebbe per std::unordered_set, ma per std::setesso.
Jabberwocky

È normale, binary_search funziona solo per alberi binari.
Massimiliano Di Cavio

0

contiene () deve restituire un valore bool. Usando il compilatore C ++ 20 ottengo il seguente output per il codice:

#include<iostream>
#include<map>
using namespace std;

int main()
{
    multimap<char,int>mulmap;
    mulmap.insert(make_pair('a', 1)); //multiple similar key
    mulmap.insert(make_pair('a', 2)); //multiple similar key
    mulmap.insert(make_pair('a', 3)); //multiple similar key
    mulmap.insert(make_pair('b', 3));
    mulmap.insert({'a',4});
    mulmap.insert(pair<char,int>('a', 4));
    
    cout<<mulmap.contains('c')<<endl;  //Output:0 as it doesn't exist
    cout<<mulmap.contains('b')<<endl;  //Output:1 as it exist
}

-1

Un altro motivo è che darebbe a un programmatore la falsa impressione che std :: set sia un insieme nel senso della teoria degli insiemi matematici. Se lo implementano, allora seguiranno molte altre domande: se uno std :: set ha contains () per un valore, perché non lo ha per un altro insieme? Dove sono union (), intersection () e altre operazioni e predicati sugli insiemi?

La risposta è, ovviamente, che alcune delle operazioni sugli insiemi sono già implementate come funzioni in (std :: set_union () ecc.) E altre sono implementate banalmente come contains (). Le funzioni e gli oggetti funzione funzionano meglio con le astrazioni matematiche rispetto ai membri degli oggetti e non sono limitati al particolare tipo di contenitore.

Se è necessario implementare una funzionalità matematica completa, non solo ha una scelta del contenitore sottostante, ma ha anche una scelta di dettagli di implementazione, ad esempio, la sua funzione theory_union () funzionerebbe con oggetti immutabili, più adatti per la programmazione funzionale , o modificherà i suoi operandi e salverà memoria? Sarebbe implementato come oggetto funzione dall'inizio o sarebbe meglio implementare una funzione C e utilizzare std :: function <> se necessario?

Allo stato attuale, std :: set è solo un contenitore, adatto per l'implementazione di set in senso matematico, ma è lontano dall'essere un insieme teorico quanto std :: vector dall'essere un vettore teorico.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.