J'ai un vecteur contenant quelques doublons non adjacents.
Comme exemple simple, considérons:
2 1 6 1 4 6 2 1 1
J'essaie de rendre cette vector
unique en supprimant les doublons non adjacents et en maintenant l'ordre des éléments.
Le résultat serait:
2 1 6 4
Les solutions que j'ai essayées sont:
Élimination manuelle des doublons:
Define a temporary vector TempVector.
for (each element in a vector)
{
if (the element does not exists in TempVector)
{
add to TempVector;
}
}
swap orginial vector with TempVector.
Ma question est:
Existe-t-il un algorithme STL capable de supprimer les doublons non adjacents du vecteur? Quelle est sa complexité?
Je pense que tu le ferais comme ça:
J'utiliserais deux itérateurs sur le vecteur:
Le premier des lecteurs lit les données et les insère dans un ensemble temporaire.
Lorsque les données lues n'étaient pas dans l'ensemble, vous les copiez du premier itérateur au deuxième et l'incrémentez.
À la fin, vous ne conservez que les données jusqu'au deuxième itérateur.
La complexité est O (n .log (n)), car la recherche d'éléments dupliqués utilise l'ensemble, pas le vecteur.
#include <vector>
#include <set>
#include <iostream>
int main(int argc, char* argv[])
{
std::vector< int > k ;
k.Push_back( 2 );
k.Push_back( 1 );
k.Push_back( 6 );
k.Push_back( 1 );
k.Push_back( 4 );
k.Push_back( 6 );
k.Push_back( 2 );
k.Push_back( 1 );
k.Push_back( 1 );
{
std::vector< int >::iterator r , w ;
std::set< int > tmpset ;
for( r = k.begin() , w = k.begin() ; r != k.end() ; ++r )
{
if( tmpset.insert( *r ).second )
{
*w++ = *r ;
}
}
k.erase( w , k.end() );
}
{
std::vector< int >::iterator r ;
for( r = k.begin() ; r != k.end() ; ++r )
{
std::cout << *r << std::endl ;
}
}
}
Sans utiliser une set
temporaire, il est possible de le faire avec (éventuellement) une perte de performance:
template<class Iterator>
Iterator Unique(Iterator first, Iterator last)
{
while (first != last)
{
Iterator next(first);
last = std::remove(++next, last, *first);
first = next;
}
return last;
}
utilisé comme dans:
vec.erase( Unique( vec.begin(), vec.end() ), vec.end() );
Pour des ensembles de données plus petits, la simplicité de mise en œuvre et l'absence d'allocation supplémentaire requise peuvent compenser la complexité théorique accrue de l'utilisation d'une set
supplémentaire. Mesurer avec une entrée représentative est cependant le seul moyen d'être sûr.
Vous pouvez supprimer certaines des boucles dans fa's answer en utilisant remove_copy_if
:
class NotSeen : public std::unary_function <int, bool>
{
public:
NotSeen (std::set<int> & seen) : m_seen (seen) { }
bool operator ()(int i) const {
return (m_seen.insert (i).second);
}
private:
std::set<int> & m_seen;
};
void removeDups (std::vector<int> const & iv, std::vector<int> & ov) {
std::set<int> seen;
std::remove_copy_if (iv.begin ()
, iv.end ()
, std::back_inserter (ov)
, NotSeen (seen));
}
Cela n'a pas d'incidence sur la complexité de l'algorithme (c'est-à-dire que, tel qu'il est écrit, il s'agit également de O (n log n)). Vous pouvez améliorer ceci en utilisant unordered_set, ou si la plage de vos valeurs est suffisamment petite, vous pouvez simplement utiliser un tableau ou un bitarray.
La question étant "existe-t-il un algorithme STL ...? Quelle est sa complexité?" il est logique d'implémenter la fonction comme std::unique
:
template <class FwdIterator>
inline FwdIterator stable_unique(FwdIterator first, FwdIterator last)
{
FwdIterator result = first;
std::unordered_set<typename FwdIterator::value_type> seen;
for (; first != last; ++first)
if (seen.insert(*first).second)
*result++ = *first;
return result;
}
Donc, voici comment std::unique
est implémenté avec un ensemble supplémentaire. Le unordered_set
doit être plus rapide qu'une set
régulière. Tous les éléments sont supprimés et se comparent égaux à l'élément qui les précède (le premier élément est conservé car nous ne pouvons pas unifier à rien). L'itérateur a renvoyé des points à la nouvelle extrémité dans la plage [first,last)
.
EDIT: La dernière phrase signifie que le conteneur lui-même n'est PAS modifié par unique
. Cela peut être déroutant. L'exemple suivant réduit réellement le conteneur à l'ensemble unifié.
1: std::vector<int> v(3, 5);
2: v.resize(std::distance(v.begin(), unique(v.begin(), v.end())));
3: assert(v.size() == 1);
La ligne 1 crée un vecteur { 5, 5, 5 }
. Dans la ligne 2, l'appel de unique
renvoie un itérateur au 2e élément, qui est le premier élément non unique. Ainsi, distance
renvoie 1 et resize
élague le vecteur.
Aucun algorithme STL ne fait ce que vous voulez, tout en préservant l'ordre d'origine de la séquence.
Vous pouvez créer un std::set
d'itérateurs ou d'index dans le vecteur, avec un prédicat de comparaison utilisant les données référencées plutôt que les itérateurs/index pour trier des éléments. Ensuite, vous supprimez tout le vecteur non référencé dans le jeu. (Bien sûr, vous pouvez aussi bien utiliser un autre std::vector
d'itérateurs/index, std::sort
et std::unique
, et l'utiliser comme référence pour ce qu'il faut conserver.)
Basé sur la réponse de @fa. Il peut également être réécrit à l'aide de l'algorithme STL std::stable_partition
:
struct dupChecker_ {
inline dupChecker_() : tmpSet() {}
inline bool operator()(int i) {
return tmpSet.insert(i).second;
}
private:
std::set<int> tmpSet;
};
k.erase(std::stable_partition(k.begin(), k.end(), dupChecker_()), k.end());
De cette façon, il est plus compact et nous n’avons pas besoin de nous occuper des itérateurs.
Il semble même ne pas introduire beaucoup de pénalité de performance. Je l’utilise dans mon projet qui doit souvent traiter des vecteurs assez volumineux et complexes, et cela ne fait aucune différence.
Une autre fonctionnalité intéressante est qu'il est possible de régler le uniqueness en utilisant std::set<int, myCmp_> tmpSet;
. Par exemple, dans mon projet, ignorer certaines erreurs d'arrondi.
John Torjo a publié un article dans Nice sur cette question de manière systématique. Le résultat auquel il aboutit semble plus générique et plus efficace qu'aucune des solutions suggérées ici jusqu'à présent:
https://web.archive.org/web/1/http://articles.techrepublic%2ecom%2ecom/5100-10878_11-1052159.html
Malheureusement, le code complet de la solution de John semble ne plus être disponible, et John n'a pas répondu à un email. Par conséquent, j'ai écrit mon propre code qui est basé sur des motifs similaires au sien, mais diffère intentionnellement de certains détails. N'hésitez pas à me contacter (vschoech think-cell com) et discuter des détails si vous le souhaitez.
Pour que le code soit compilé pour vous, j'ai ajouté des éléments de ma propre bibliothèque que j'utilise régulièrement. De plus, au lieu d’utiliser plain stl, j’utilise beaucoup boost pour créer du code plus générique, plus efficace et plus lisible.
S'amuser!
#include <vector>
#include <functional>
#include <boost/bind.hpp>
#include <boost/range.hpp>
#include <boost/iterator/counting_iterator.hpp>
/////////////////////////////////////////////////////////////////////////////////////////////
// library stuff
template< class Rng, class Func >
Func for_each( Rng& rng, Func f ) {
return std::for_each( boost::begin(rng), boost::end(rng), f );
};
template< class Rng, class Pred >
Rng& sort( Rng& rng, Pred pred ) {
std::sort( boost::begin( rng ), boost::end( rng ), pred );
return rng; // to allow function chaining, similar to operator+= et al.
}
template< class T >
boost::iterator_range< boost::counting_iterator<T> > make_counting_range( T const& tBegin, T const& tEnd ) {
return boost::iterator_range< boost::counting_iterator<T> >( tBegin, tEnd );
}
template< class Func >
class compare_less_impl {
private:
Func m_func;
public:
typedef bool result_type;
compare_less_impl( Func func )
: m_func( func )
{}
template< class T1, class T2 > bool operator()( T1 const& tLeft, T2 const& tRight ) const {
return m_func( tLeft ) < m_func( tRight );
}
};
template< class Func >
compare_less_impl<Func> compare_less( Func func ) {
return compare_less_impl<Func>( func );
}
/////////////////////////////////////////////////////////////////////////////////////////////
// stable_unique
template<class forward_iterator, class predicate_type>
forward_iterator stable_unique(forward_iterator itBegin, forward_iterator itEnd, predicate_type predLess) {
typedef std::iterator_traits<forward_iterator>::difference_type index_type;
struct SIteratorIndex {
SIteratorIndex(forward_iterator itValue, index_type idx) : m_itValue(itValue), m_idx(idx) {}
std::iterator_traits<forward_iterator>::reference Value() const {return *m_itValue;}
index_type m_idx;
private:
forward_iterator m_itValue;
};
// {1} create array of values (represented by iterators) and indices
std::vector<SIteratorIndex> vecitidx;
vecitidx.reserve( std::distance(itBegin, itEnd) );
struct FPushBackIteratorIndex {
FPushBackIteratorIndex(std::vector<SIteratorIndex>& vecitidx) : m_vecitidx(vecitidx) {}
void operator()(forward_iterator itValue) const {
m_vecitidx.Push_back( SIteratorIndex(itValue, m_vecitidx.size()) );
}
private:
std::vector<SIteratorIndex>& m_vecitidx;
};
for_each( make_counting_range(itBegin, itEnd), FPushBackIteratorIndex(vecitidx) );
// {2} sort by underlying value
struct FStableCompareByValue {
FStableCompareByValue(predicate_type predLess) : m_predLess(predLess) {}
bool operator()(SIteratorIndex const& itidxA, SIteratorIndex const& itidxB) {
return m_predLess(itidxA.Value(), itidxB.Value())
// stable sort order, index is secondary criterion
|| !m_predLess(itidxB.Value(), itidxA.Value()) && itidxA.m_idx < itidxB.m_idx;
}
private:
predicate_type m_predLess;
};
sort( vecitidx, FStableCompareByValue(predLess) );
// {3} apply std::unique to the sorted vector, removing duplicate values
vecitidx.erase(
std::unique( vecitidx.begin(), vecitidx.end(),
!boost::bind( predLess,
// redundand boost::mem_fn required to compile
boost::bind(boost::mem_fn(&SIteratorIndex::Value), _1),
boost::bind(boost::mem_fn(&SIteratorIndex::Value), _2)
)
),
vecitidx.end()
);
// {4} re-sort by index to match original order
sort( vecitidx, compare_less(boost::mem_fn(&SIteratorIndex::m_idx)) );
// {5} keep only those values in the original range that were not removed by std::unique
std::vector<SIteratorIndex>::iterator ititidx = vecitidx.begin();
forward_iterator itSrc = itBegin;
index_type idx = 0;
for(;;) {
if( ititidx==vecitidx.end() ) {
// {6} return end of unique range
return itSrc;
}
if( idx!=ititidx->m_idx ) {
// original range must be modified
break;
}
++ititidx;
++idx;
++itSrc;
}
forward_iterator itDst = itSrc;
do {
++idx;
++itSrc;
// while there are still items in vecitidx, there must also be corresponding items in the original range
if( idx==ititidx->m_idx ) {
std::swap( *itDst, *itSrc ); // C++0x move
++ititidx;
++itDst;
}
} while( ititidx!=vecitidx.end() );
// {6} return end of unique range
return itDst;
}
template<class forward_iterator>
forward_iterator stable_unique(forward_iterator itBegin, forward_iterator itEnd) {
return stable_unique( itBegin, itEnd, std::less< std::iterator_traits<forward_iterator>::value_type >() );
}
void stable_unique_test() {
std::vector<int> vecn;
vecn.Push_back(1);
vecn.Push_back(17);
vecn.Push_back(-100);
vecn.Push_back(17);
vecn.Push_back(1);
vecn.Push_back(17);
vecn.Push_back(53);
vecn.erase( stable_unique(vecn.begin(), vecn.end()), vecn.end() );
// result: 1, 17, -100, 53
}
Ma question est:
Existe-t-il un algorithme STL capable de supprimer les doublons non adjacents du vecteur? Quelle est sa complexité?
Les options STL sont celles que vous avez mentionnées: placez les éléments dans un std::set
ou appelez std::sort
, std::unique
et appelez erase()
sur le conteneur. Aucune de celles-ci ne répond à votre exigence de "supprimer les doublons non adjacents et de maintenir l'ordre des éléments".
Alors pourquoi la STL n'offre-t-elle pas une autre option? Aucune bibliothèque standard n'offrira tout pour les besoins de chaque utilisateur. Les considérations de conception du STL incluent "être assez rapide pour presque tous les utilisateurs", "être utile pour presque tous les utilisateurs" et "assurer la sécurité des exceptions autant que possible" (et "être suffisamment petit pour le Comité de normalisation" comme la bibliothèque de Stepanov à l'origine était beaucoup plus grand et Stroustrup en a éliminé environ les 2/3).
La solution la plus simple à laquelle je puisse penser ressemble à ceci:
// Note: an STL-like method would be templatized and use iterators instead of
// hardcoding std::vector<int>
std::vector<int> stable_unique(const std::vector<int>& input)
{
std::vector<int> result;
result.reserve(input.size());
for (std::vector<int>::iterator itor = input.begin();
itor != input.end();
++itor)
if (std::find(result.begin(), result.end(), *itor) == result.end())
result.Push_back(*itor);
return result;
}
Cette solution doit être O (M * N) où M est le nombre d'éléments uniques et N le nombre total d'éléments.
Pour autant que je sache, il n'y en a pas en stl. Recherchez référence .
Basé sur la réponse de @ Corden, mais utilise l'expression lambda et supprime les doublons au lieu de les écrire dans le vecteur de sortie
set<int> s;
vector<int> nodups;
remove_copy_if(v.begin(), v.end(), back_inserter(nodups),
[&s](int x){
return !s.insert(x).second; //-> .second false means already exists
} );