Qual è il modo più efficiente per cancellare i duplicati e ordinare un vettore?


274

Ho bisogno di prendere un vettore C ++ con potenzialmente molti elementi, cancellare i duplicati e ordinarlo.

Al momento ho il codice seguente, ma non funziona.

vec.erase(
      std::unique(vec.begin(), vec.end()),
      vec.end());
std::sort(vec.begin(), vec.end());

Come posso farlo correttamente?

Inoltre, è più veloce cancellare prima i duplicati (simile al codice sopra) o eseguire prima l'ordinamento? Se eseguo prima l'ordinamento, è garantito che rimanga ordinato dopo l' std::uniqueesecuzione?

O c'è un altro (forse più efficiente) modo per fare tutto questo?


3
Suppongo che non hai la possibilità di controllare prima di inserire per evitare di avere duplicati in primo luogo?
Joe,

Corretta. Sarebbe l'ideale.
Kyle Ryan,

29
Suggerirei di correggere il codice sopra o indicherei davvero che è SBAGLIATO. std :: unique presuppone che l'intervallo sia già ordinato.
Matthieu M.,

Risposte:


584

Sono d'accordo con R. Pate e Todd Gardner ; una std::setpotrebbe essere una buona idea qui. Anche se sei bloccato usando i vettori, se hai abbastanza duplicati, potresti fare meglio a creare un set per fare il lavoro sporco.

Confrontiamo tre approcci:

Basta usare vector, sort + unique

sort( vec.begin(), vec.end() );
vec.erase( unique( vec.begin(), vec.end() ), vec.end() );

Converti in set (manualmente)

set<int> s;
unsigned size = vec.size();
for( unsigned i = 0; i < size; ++i ) s.insert( vec[i] );
vec.assign( s.begin(), s.end() );

Converti in set (usando un costruttore)

set<int> s( vec.begin(), vec.end() );
vec.assign( s.begin(), s.end() );

Ecco come si comportano quando il numero di duplicati cambia:

confronto di approcci vettoriali e impostati

Riepilogo : quando il numero di duplicati è abbastanza grande, in realtà è più veloce convertire in un set e quindi scaricare i dati in un vettore .

E per qualche motivo, eseguire manualmente la conversione del set sembra essere più veloce rispetto all'utilizzo del costruttore del set - almeno sui dati casuali giocattolo che ho usato.


61
Sono scioccato dal fatto che l'approccio del costruttore sia costantemente peggio di quello manuale. A parte questo, qualche piccolo sovraccarico costante, farebbe solo la cosa manuale. Qualcuno può spiegare questo?
Ari,

17
Fantastico, grazie per il grafico. Potresti dare un'idea di cosa sono le unità per Numero di duplicati? (ovvero, quanto è grande "abbastanza grande")?
Kyle Ryan,

5
@Kyle: è piuttosto grande. Per questo grafico ho usato set di dati di 1.000.000 di numeri interi disegnati casualmente tra 1 e 1000, 100 e 10.
Nate Kohl,

5
Penso che i tuoi risultati siano sbagliati. Nei miei test più elementi duplicati sono il vettore più veloce (comparativo), in realtà si ridimensiona al contrario. Hai compilato con ottimizzazioni attivate e controlli di runtime disattivati ​​?. Dalla mia parte il vettore è sempre più veloce, fino a 100 volte a seconda del numero di duplicati. VS2013, cl / Ox -D_SECURE_SCL = 0.
davidnr,

39
La descrizione dell'asse x sembra mancare.
BartoszKP,

72

Ho rifatto la profilazione di Nate Kohl e ho ottenuto risultati diversi. Per il mio caso di prova, l'ordinamento diretto del vettore è sempre più efficiente rispetto all'utilizzo di un set. Ho aggiunto un nuovo metodo più efficiente, usando un unordered_set.

Tieni presente che il unordered_setmetodo funziona solo se hai una buona funzione hash per il tipo di cui hai bisogno in modo univoco e ordinato. Per gli inte, questo è facile! (La libreria standard fornisce un hash predefinito che è semplicemente la funzione identità.) Inoltre, non dimenticare di ordinare alla fine poiché unordered_set è, beh, non ordinato :)

Ho fatto qualche ricerca all'interno dell'implementazione sete ho unordered_setscoperto che il costruttore in realtà costruisce un nuovo nodo per ogni elemento, prima di controllarne il valore per determinare se debba essere effettivamente inserito (almeno nell'implementazione di Visual Studio).

Ecco i 5 metodi:

f1: basta usare vector, sort+unique

sort( vec.begin(), vec.end() );
vec.erase( unique( vec.begin(), vec.end() ), vec.end() );

f2: Converti in set(usando un costruttore)

set<int> s( vec.begin(), vec.end() );
vec.assign( s.begin(), s.end() );

f3: Converti in set(manualmente)

set<int> s;
for (int i : vec)
    s.insert(i);
vec.assign( s.begin(), s.end() );

f4: Converti in unordered_set(usando un costruttore)

unordered_set<int> s( vec.begin(), vec.end() );
vec.assign( s.begin(), s.end() );
sort( vec.begin(), vec.end() );

f5: converti in unordered_set(manualmente)

unordered_set<int> s;
for (int i : vec)
    s.insert(i);
vec.assign( s.begin(), s.end() );
sort( vec.begin(), vec.end() );

Ho fatto il test con un vettore di 100.000.000 di pollici scelti casualmente negli intervalli [1,10], [1,1000] e [1.100000]

I risultati (in pochi secondi, meglio è piccolo):

range         f1       f2       f3       f4      f5
[1,10]      1.6821   7.6804   2.8232   6.2634  0.7980
[1,1000]    5.0773  13.3658   8.2235   7.6884  1.9861
[1,100000]  8.7955  32.1148  26.5485  13.3278  3.9822

4
Per i numeri interi, puoi usare radix sort, che è molto più veloce di std :: sort.
Changming Sun,

2
sortunique#include <algorithm>
Suggerimento

3
@ChangmingSun Mi chiedo perché l'ottimizzatore sembra fallire su f4? I numeri sono notevolmente diversi da f5. Non ha alcun senso per me.
Sandthorn,

1
@sandthorn Come spiegato nella mia risposta, l'implementazione crea un nodo (inclusa l'allocazione dinamica) per ogni elemento della sequenza di input, che è dispendioso per ogni valore che finisce per essere un duplicato. L'ottimizzatore non può assolutamente sapere che potrebbe saltarlo.
alexk7,

Ah, questo mi ricorda uno dei discorsi di Scott Meyer sullo CWUK scenerio che ha una natura di propensione a rallentare il emplacetipo di costruzione.
sandthorn,

58

std::unique rimuove gli elementi duplicati solo se sono vicini: devi prima ordinare il vettore prima che funzioni come previsto.

std::unique è definito come stabile, quindi il vettore verrà comunque ordinato dopo l'esecuzione univoca su di esso.


42

Non sono sicuro di cosa tu stia usando questo, quindi non posso dirlo con certezza al 100%, ma normalmente quando penso a un contenitore "ordinato, unico", penso a uno std :: set . Potrebbe adattarsi meglio al tuo caso d'uso:

std::set<Foo> foos(vec.begin(), vec.end()); // both sorted & unique already

Altrimenti, l'ordinamento prima di chiamare unico (come sottolineato dalle altre risposte) è la strada da percorrere.


Bene al punto! std :: set è specificato per essere un set univoco ordinato. La maggior parte delle implementazioni utilizza un albero binario ordinato efficiente o qualcosa di simile.
notnoop,

+1 Anche pensato al set. Non volevo duplicare questa risposta
Tom

Std :: set è garantito per essere ordinato? Ha senso che in pratica lo sarebbe, ma lo standard lo richiede?
MadCoder,

1
Sì, vedi 23.1.4.9 "La proprietà fondamentale degli iteratori di contenitori associativi è quella di iterare attraverso i contenitori nell'ordine non discendente delle chiavi dove non discendente è definito dal confronto utilizzato per costruirle"
Todd Gardner

1
@MadCoder: non ha necessariamente "senso" che un set sia implementato in modo ordinato. Ci sono anche insiemi implementati usando le tabelle hash, che non sono ordinate. In effetti, la maggior parte delle persone preferisce usare le tabelle hash quando disponibili. Ma la convenzione di denominazione in C ++ accade che i contenitori associativi ordinati vengano semplicemente denominati "set" / "map" (analogo a TreeSet / TreeMap in Java); e i contenitori associativi con hash, che sono stati lasciati fuori dallo standard, sono chiamati "hash_set" / "hash_map" (SGI STL) o "unordered_set" / "unordered_map" (TR1) (analogo a HashSet e HashMap in Java)
newacct

22

std::uniquefunziona solo su esecuzioni consecutive di elementi duplicati, quindi è meglio ordinare prima. Tuttavia, è stabile, quindi il tuo vettore rimarrà ordinato.


18

Ecco un modello per farlo per te:

template<typename T>
void removeDuplicates(std::vector<T>& vec)
{
    std::sort(vec.begin(), vec.end());
    vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
}

chiamalo come:

removeDuplicates<int>(vectorname);

2
+1 Templatize away! - ma puoi semplicemente scrivere removeDuplicates (vec), senza specificare esplicitamente gli argomenti del template
Faisal Vali,

10
O ancora meglio, basta che prenda direttamente gli iteratori di modello (inizio e fine) e puoi eseguirlo su altre strutture oltre a un vettore.
Kyle Ryan,

Hells sì, modelli! soluzione rapida per piccoli elenchi, stile STL completo. +1 grazie
QuantumKarl

@Kyle: solo su altri contenitori che dispongono di un erase()metodo, altrimenti è necessario restituire il nuovo iteratore finale e far troncare il contenitore dal codice chiamante.
Toby Speight,

8

L'efficienza è un concetto complicato. Esistono considerazioni sul tempo e sullo spazio, nonché misurazioni generali (in cui si ottengono solo risposte vaghe come O (n)) rispetto a quelle specifiche (ad esempio, l'ordinamento delle bolle può essere molto più veloce di quicksort, a seconda delle caratteristiche di input).

Se hai relativamente pochi duplicati, allora l'ordinamento seguito da unico e cancella sembra la strada da percorrere. Se avessi relativamente molti duplicati, creare un set dal vettore e lasciarlo fare il sollevamento pesante potrebbe facilmente batterlo.

Non concentrarti solo sull'efficienza temporale. Sort + unique + erase opera nello spazio O (1), mentre la costruzione impostata opera nello spazio O (n). E nessuno dei due si presta direttamente a una parallelizzazione di riduzione della mappa (per set di dati davvero enormi ).


Cosa ti darebbe la mappa / ridurrai l'abilità? L'unico che mi viene in mente è un ordinamento di unione distribuito e puoi ancora utilizzare solo un thread nell'unione finale.
Zan Lynx,

1
Sì, è necessario disporre di un nodo / thread di controllo. Tuttavia, è possibile dividere il problema tutte le volte necessarie per posizionare i limiti superiori sul numero di thread worker / child con cui si occupa il thread controllore / parent e sulla dimensione del set di dati che ciascun nodo foglia deve elaborare. Non tutti i problemi sono facilmente risolvibili con la riduzione della mappa, volevo semplicemente sottolineare che ci sono persone che si occupano di problemi simili (in superficie, comunque) di ottimizzazione, in cui trattare 10 terabyte di dati è chiamato "Martedì".

7

Devi ordinarlo prima di chiamare uniqueperché uniquerimuove solo i duplicati uno accanto all'altro.

modifica: 38 secondi ...


7

uniquerimuove solo gli elementi duplicati consecutivi (che è necessario per l'esecuzione in tempo lineare), quindi è necessario eseguire prima l'ordinamento. Rimarrà ordinato dopo la chiamata a unique.


7

Se non vuoi cambiare l'ordine degli elementi, puoi provare questa soluzione:

template <class T>
void RemoveDuplicatesInVector(std::vector<T> & vec)
{
    set<T> values;
    vec.erase(std::remove_if(vec.begin(), vec.end(), [&](const T & value) { return !values.insert(value).second; }), vec.end());
}

Forse usa unordered_set invece di set (e boost :: remove_erase_if se disponibile)
gast128

4

Supponendo che a sia un vettore, rimuovere i duplicati contigui utilizzando

a.erase(unique(a.begin(),a.end()),a.end());funziona in O (n) tempo.


1
duplicati contigui. ok, quindi ha bisogno di un std::sortprimo.
v.oddou,

2

Come già detto, uniquerichiede un contenitore ordinato. Inoltre, in uniquerealtà non rimuove gli elementi dal contenitore. Invece, vengono copiati fino alla fine, uniquerestituisce un iteratore che punta al primo di tali elementi duplicati e ci si aspetta che tu chiami eraseper rimuovere effettivamente gli elementi.


Univoco richiede un contenitore ordinato o riorganizza semplicemente la sequenza di input in modo che non contenga duplicati adiacenti? Ho pensato a quest'ultimo.

@Pate, hai ragione. Non richiede uno. Rimuove i duplicati adiacenti.
Bill Lynch,

Se si dispone di un contenitore che può avere duplicati e si desidera un contenitore che non ha valori duplicati in qualsiasi punto del contenitore, è necessario prima ordinare il contenitore, quindi passarlo a univoco e quindi utilizzare la cancellazione per rimuovere effettivamente i duplicati . Se desideri semplicemente rimuovere i duplicati adiacenti, non dovrai ordinare il contenitore. Ma finirai con valori duplicati: 1 2 2 3 2 4 2 5 2 verrà modificato in 1 2 3 2 4 2 5 2 se passato a unico senza ordinamento, 1 2 3 4 5 se ordinato, passato a unico e cancella .
Max Lybbert,

2

L'approccio standard suggerito da Nate Kohl, usando solo vector, sort + unique:

sort( vec.begin(), vec.end() );
vec.erase( unique( vec.begin(), vec.end() ), vec.end() );

non funziona per un vettore di puntatori.

Guarda attentamente questo esempio su cplusplus.com .

Nel loro esempio, i "cosiddetti duplicati" spostati alla fine vengono effettivamente visualizzati come? (valori indefiniti), poiché quei "cosiddetti duplicati" sono ALCUNI "elementi extra" e ALCUNI ci sono "elementi mancanti" che erano nel vettore originale.

Si verifica un problema quando si utilizza std::unique()un vettore di puntatori agli oggetti (perdite di memoria, lettura errata dei dati da HEAP, liberazioni duplicate, che causano errori di segmentazione, ecc.).

Ecco la mia soluzione al problema: sostituire std::unique()con ptgi::unique().

Vedi il file ptgi_unique.hpp di seguito:

// ptgi::unique()
//
// Fix a problem in std::unique(), such that none of the original elts in the collection are lost or duplicate.
// ptgi::unique() has the same interface as std::unique()
//
// There is the 2 argument version which calls the default operator== to compare elements.
//
// There is the 3 argument version, which you can pass a user defined functor for specialized comparison.
//
// ptgi::unique() is an improved version of std::unique() which doesn't looose any of the original data
// in the collection, nor does it create duplicates.
//
// After ptgi::unique(), every old element in the original collection is still present in the re-ordered collection,
// except that duplicates have been moved to a contiguous range [dupPosition, last) at the end.
//
// Thus on output:
//  [begin, dupPosition) range are unique elements.
//  [dupPosition, last) range are duplicates which can be removed.
// where:
//  [] means inclusive, and
//  () means exclusive.
//
// In the original std::unique() non-duplicates at end are moved downward toward beginning.
// In the improved ptgi:unique(), non-duplicates at end are swapped with duplicates near beginning.
//
// In addition if you have a collection of ptrs to objects, the regular std::unique() will loose memory,
// and can possibly delete the same pointer multiple times (leading to SEGMENTATION VIOLATION on Linux machines)
// but ptgi::unique() won't.  Use valgrind(1) to find such memory leak problems!!!
//
// NOTE: IF you have a vector of pointers, that is, std::vector<Object*>, then upon return from ptgi::unique()
// you would normally do the following to get rid of the duplicate objects in the HEAP:
//
//  // delete objects from HEAP
//  std::vector<Object*> objects;
//  for (iter = dupPosition; iter != objects.end(); ++iter)
//  {
//      delete (*iter);
//  }
//
//  // shrink the vector. But Object * pointers are NOT followed for duplicate deletes, this shrinks the vector.size())
//  objects.erase(dupPosition, objects.end));
//
// NOTE: But if you have a vector of objects, that is: std::vector<Object>, then upon return from ptgi::unique(), it
// suffices to just call vector:erase(, as erase will automatically call delete on each object in the
// [dupPosition, end) range for you:
//
//  std::vector<Object> objects;
//  objects.erase(dupPosition, last);
//
//==========================================================================================================
// Example of differences between std::unique() vs ptgi::unique().
//
//  Given:
//      int data[] = {10, 11, 21};
//
//  Given this functor: ArrayOfIntegersEqualByTen:
//      A functor which compares two integers a[i] and a[j] in an int a[] array, after division by 10:
//  
//  // given an int data[] array, remove consecutive duplicates from it.
//  // functor used for std::unique (BUGGY) or ptgi::unique(IMPROVED)
//
//  // Two numbers equal if, when divided by 10 (integer division), the quotients are the same.
//  // Hence 50..59 are equal, 60..69 are equal, etc.
//  struct ArrayOfIntegersEqualByTen: public std::equal_to<int>
//  {
//      bool operator() (const int& arg1, const int& arg2) const
//      {
//          return ((arg1/10) == (arg2/10));
//      }
//  };
//  
//  Now, if we call (problematic) std::unique( data, data+3, ArrayOfIntegersEqualByTen() );
//  
//  TEST1: BEFORE UNIQ: 10,11,21
//  TEST1: AFTER UNIQ: 10,21,21
//  DUP_INX=2
//  
//      PROBLEM: 11 is lost, and extra 21 has been added.
//  
//  More complicated example:
//  
//  TEST2: BEFORE UNIQ: 10,20,21,22,30,31,23,24,11
//  TEST2: AFTER UNIQ: 10,20,30,23,11,31,23,24,11
//  DUP_INX=5
//  
//      Problem: 21 and 22 are deleted.
//      Problem: 11 and 23 are duplicated.
//  
//  
//  NOW if ptgi::unique is called instead of std::unique, both problems go away:
//  
//  DEBUG: TEST1: NEW_WAY=1
//  TEST1: BEFORE UNIQ: 10,11,21
//  TEST1: AFTER UNIQ: 10,21,11
//  DUP_INX=2
//  
//  DEBUG: TEST2: NEW_WAY=1
//  TEST2: BEFORE UNIQ: 10,20,21,22,30,31,23,24,11
//  TEST2: AFTER UNIQ: 10,20,30,23,11,31,22,24,21
//  DUP_INX=5
//
//  @SEE: look at the "case study" below to understand which the last "AFTER UNIQ" results with that order:
//  TEST2: AFTER UNIQ: 10,20,30,23,11,31,22,24,21
//
//==========================================================================================================
// Case Study: how ptgi::unique() works:
//  Remember we "remove adjacent duplicates".
//  In this example, the input is NOT fully sorted when ptgi:unique() is called.
//
//  I put | separatators, BEFORE UNIQ to illustrate this
//  10  | 20,21,22 |  30,31 |  23,24 | 11
//
//  In example above, 20, 21, 22 are "same" since dividing by 10 gives 2 quotient.
//  And 30,31 are "same", since /10 quotient is 3.
//  And 23, 24 are same, since /10 quotient is 2.
//  And 11 is "group of one" by itself.
//  So there are 5 groups, but the 4th group (23, 24) happens to be equal to group 2 (20, 21, 22)
//  So there are 5 groups, and the 5th group (11) is equal to group 1 (10)
//
//  R = result
//  F = first
//
//  10, 20, 21, 22, 30, 31, 23, 24, 11
//  R    F
//
//  10 is result, and first points to 20, and R != F (10 != 20) so bump R:
//       R
//       F
//
//  Now we hits the "optimized out swap logic".
//  (avoid swap because R == F)
//
//  // now bump F until R != F (integer division by 10)
//  10, 20, 21, 22, 30, 31, 23, 24, 11
//       R   F              // 20 == 21 in 10x
//       R       F              // 20 == 22 in 10x
//       R           F          // 20 != 30, so we do a swap of ++R and F
//  (Now first hits 21, 22, then finally 30, which is different than R, so we swap bump R to 21 and swap with  30)
//  10, 20, 30, 22, 21, 31, 23, 24, 11  // after R & F swap (21 and 30)
//           R       F 
//
//  10, 20, 30, 22, 21, 31, 23, 24, 11
//           R          F           // bump F to 31, but R and F are same (30 vs 31)
//           R               F      // bump F to 23, R != F, so swap ++R with F
//  10, 20, 30, 22, 21, 31, 23, 24, 11
//                  R           F       // bump R to 22
//  10, 20, 30, 23, 21, 31, 22, 24, 11  // after the R & F swap (22 & 23 swap)
//                  R            F      // will swap 22 and 23
//                  R                F      // bump F to 24, but R and F are same in 10x
//                  R                    F  // bump F, R != F, so swap ++R  with F
//                      R                F  // R and F are diff, so swap ++R  with F (21 and 11)
//  10, 20, 30, 23, 11, 31, 22, 24, 21
//                      R                F  // aftter swap of old 21 and 11
//                      R                  F    // F now at last(), so loop terminates
//                          R               F   // bump R by 1 to point to dupPostion (first duplicate in range)
//
//  return R which now points to 31
//==========================================================================================================
// NOTES:
// 1) the #ifdef IMPROVED_STD_UNIQUE_ALGORITHM documents how we have modified the original std::unique().
// 2) I've heavily unit tested this code, including using valgrind(1), and it is *believed* to be 100% defect-free.
//
//==========================================================================================================
// History:
//  130201  dpb dbednar@ptgi.com created
//==========================================================================================================

#ifndef PTGI_UNIQUE_HPP
#define PTGI_UNIQUE_HPP

// Created to solve memory leak problems when calling std::unique() on a vector<Route*>.
// Memory leaks discovered with valgrind and unitTesting.


#include <algorithm>        // std::swap

// instead of std::myUnique, call this instead, where arg3 is a function ptr
//
// like std::unique, it puts the dups at the end, but it uses swapping to preserve original
// vector contents, to avoid memory leaks and duplicate pointers in vector<Object*>.

#ifdef IMPROVED_STD_UNIQUE_ALGORITHM
#error the #ifdef for IMPROVED_STD_UNIQUE_ALGORITHM was defined previously.. Something is wrong.
#endif

#undef IMPROVED_STD_UNIQUE_ALGORITHM
#define IMPROVED_STD_UNIQUE_ALGORITHM

// similar to std::unique, except that this version swaps elements, to avoid
// memory leaks, when vector contains pointers.
//
// Normally the input is sorted.
// Normal std::unique:
// 10 20 20 20 30   30 20 20 10
// a  b  c  d  e    f  g  h  i
//
// 10 20 30 20 10 | 30 20 20 10
// a  b  e  g  i    f  g  h  i
//
// Now GONE: c, d.
// Now DUPS: g, i.
// This causes memory leaks and segmenation faults due to duplicate deletes of same pointer!


namespace ptgi {

// Return the position of the first in range of duplicates moved to end of vector.
//
// uses operator==  of class for comparison
//
// @param [first, last) is a range to find duplicates within.
//
// @return the dupPosition position, such that [dupPosition, end) are contiguous
// duplicate elements.
// IF all items are unique, then it would return last.
//
template <class ForwardIterator>
ForwardIterator unique( ForwardIterator first, ForwardIterator last)
{
    // compare iterators, not values
    if (first == last)
        return last;

    // remember the current item that we are looking at for uniqueness
    ForwardIterator result = first;

    // result is slow ptr where to store next unique item
    // first is  fast ptr which is looking at all elts

    // the first iterator moves over all elements [begin+1, end).
    // while the current item (result) is the same as all elts
    // to the right, (first) keeps going, until you find a different
    // element pointed to by *first.  At that time, we swap them.

    while (++first != last)
    {
        if (!(*result == *first))
        {
#ifdef IMPROVED_STD_UNIQUE_ALGORITHM
            // inc result, then swap *result and *first

//          THIS IS WHAT WE WANT TO DO.
//          BUT THIS COULD SWAP AN ELEMENT WITH ITSELF, UNCECESSARILY!!!
//          std::swap( *first, *(++result));

            // BUT avoid swapping with itself when both iterators are the same
            ++result;
            if (result != first)
                std::swap( *first, *result);
#else
            // original code found in std::unique()
            // copies unique down
            *(++result) = *first;
#endif
        }
    }

    return ++result;
}

template <class ForwardIterator, class BinaryPredicate>
ForwardIterator unique( ForwardIterator first, ForwardIterator last, BinaryPredicate pred)
{
    if (first == last)
        return last;

    // remember the current item that we are looking at for uniqueness
    ForwardIterator result = first;

    while (++first != last)
    {
        if (!pred(*result,*first))
        {
#ifdef IMPROVED_STD_UNIQUE_ALGORITHM
            // inc result, then swap *result and *first

//          THIS COULD SWAP WITH ITSELF UNCECESSARILY
//          std::swap( *first, *(++result));
//
            // BUT avoid swapping with itself when both iterators are the same
            ++result;
            if (result != first)
                std::swap( *first, *result);

#else
            // original code found in std::unique()
            // copies unique down
            // causes memory leaks, and duplicate ptrs
            // and uncessarily moves in place!
            *(++result) = *first;
#endif
        }
    }

    return ++result;
}

// from now on, the #define is no longer needed, so get rid of it
#undef IMPROVED_STD_UNIQUE_ALGORITHM

} // end ptgi:: namespace

#endif

Ed ecco il programma UNIT Test che ho usato per testarlo:

// QUESTION: in test2, I had trouble getting one line to compile,which was caused  by the declaration of operator()
// in the equal_to Predicate.  I'm not sure how to correctly resolve that issue.
// Look for //OUT lines
//
// Make sure that NOTES in ptgi_unique.hpp are correct, in how we should "cleanup" duplicates
// from both a vector<Integer> (test1()) and vector<Integer*> (test2).
// Run this with valgrind(1).
//
// In test2(), IF we use the call to std::unique(), we get this problem:
//
//  [dbednar@ipeng8 TestSortRoutes]$ ./Main7
//  TEST2: ORIG nums before UNIQUE: 10, 20, 21, 22, 30, 31, 23, 24, 11
//  TEST2: modified nums AFTER UNIQUE: 10, 20, 30, 23, 11, 31, 23, 24, 11
//  INFO: dupInx=5
//  TEST2: uniq = 10
//  TEST2: uniq = 20
//  TEST2: uniq = 30
//  TEST2: uniq = 33427744
//  TEST2: uniq = 33427808
//  Segmentation fault (core dumped)
//
// And if we run valgrind we seen various error about "read errors", "mismatched free", "definitely lost", etc.
//
//  valgrind --leak-check=full ./Main7
//  ==359== Memcheck, a memory error detector
//  ==359== Command: ./Main7
//  ==359== Invalid read of size 4
//  ==359== Invalid free() / delete / delete[]
//  ==359== HEAP SUMMARY:
//  ==359==     in use at exit: 8 bytes in 2 blocks
//  ==359== LEAK SUMMARY:
//  ==359==    definitely lost: 8 bytes in 2 blocks
// But once we replace the call in test2() to use ptgi::unique(), all valgrind() error messages disappear.
//
// 130212   dpb dbednar@ptgi.com created
// =========================================================================================================

#include <iostream> // std::cout, std::cerr
#include <string>
#include <vector>   // std::vector
#include <sstream>  // std::ostringstream
#include <algorithm>    // std::unique()
#include <functional>   // std::equal_to(), std::binary_function()
#include <cassert>  // assert() MACRO

#include "ptgi_unique.hpp"  // ptgi::unique()



// Integer is small "wrapper class" around a primitive int.
// There is no SETTER, so Integer's are IMMUTABLE, just like in JAVA.

class Integer
{
private:
    int num;
public:

    // default CTOR: "Integer zero;"
    // COMPRENSIVE CTOR:  "Integer five(5);"
    Integer( int num = 0 ) :
        num(num)
    {
    }

    // COPY CTOR
    Integer( const Integer& rhs) :
        num(rhs.num)
    {
    }

    // assignment, operator=, needs nothing special... since all data members are primitives

    // GETTER for 'num' data member
    // GETTER' are *always* const
    int getNum() const
    {
        return num;
    }   

    // NO SETTER, because IMMUTABLE (similar to Java's Integer class)

    // @return "num"
    // NB: toString() should *always* be a const method
    //
    // NOTE: it is probably more efficient to call getNum() intead
    // of toString() when printing a number:
    //
    // BETTER to do this:
    //  Integer five(5);
    //  std::cout << five.getNum() << "\n"
    // than this:
    //  std::cout << five.toString() << "\n"

    std::string toString() const
    {
        std::ostringstream oss;
        oss << num;
        return oss.str();
    }
};

// convenience typedef's for iterating over std::vector<Integer>
typedef std::vector<Integer>::iterator      IntegerVectorIterator;
typedef std::vector<Integer>::const_iterator    ConstIntegerVectorIterator;

// convenience typedef's for iterating over std::vector<Integer*>
typedef std::vector<Integer*>::iterator     IntegerStarVectorIterator;
typedef std::vector<Integer*>::const_iterator   ConstIntegerStarVectorIterator;

// functor used for std::unique or ptgi::unique() on a std::vector<Integer>
// Two numbers equal if, when divided by 10 (integer division), the quotients are the same.
// Hence 50..59 are equal, 60..69 are equal, etc.
struct IntegerEqualByTen: public std::equal_to<Integer>
{
    bool operator() (const Integer& arg1, const Integer& arg2) const
    {
        return ((arg1.getNum()/10) == (arg2.getNum()/10));
    }
};

// functor used for std::unique or ptgi::unique on a std::vector<Integer*>
// Two numbers equal if, when divided by 10 (integer division), the quotients are the same.
// Hence 50..59 are equal, 60..69 are equal, etc.
struct IntegerEqualByTenPointer: public std::equal_to<Integer*>
{
    // NB: the Integer*& looks funny to me!
    // TECHNICAL PROBLEM ELSEWHERE so had to remove the & from *&
//OUT   bool operator() (const Integer*& arg1, const Integer*& arg2) const
//
    bool operator() (const Integer* arg1, const Integer* arg2) const
    {
        return ((arg1->getNum()/10) == (arg2->getNum()/10));
    }
};

void test1();
void test2();
void printIntegerStarVector( const std::string& msg, const std::vector<Integer*>& nums );

int main()
{
    test1();
    test2();
    return 0;
}

// test1() uses a vector<Object> (namely vector<Integer>), so there is no problem with memory loss
void test1()
{
    int data[] = { 10, 20, 21, 22, 30, 31, 23, 24, 11};

    // turn C array into C++ vector
    std::vector<Integer> nums(data, data+9);

    // arg3 is a functor
    IntegerVectorIterator dupPosition = ptgi::unique( nums.begin(), nums.end(), IntegerEqualByTen() );

    nums.erase(dupPosition, nums.end());

    nums.erase(nums.begin(), dupPosition);
}

//==================================================================================
// test2() uses a vector<Integer*>, so after ptgi:unique(), we have to be careful in
// how we eliminate the duplicate Integer objects stored in the heap.
//==================================================================================
void test2()
{
    int data[] = { 10, 20, 21, 22, 30, 31, 23, 24, 11};

    // turn C array into C++ vector of Integer* pointers
    std::vector<Integer*> nums;

    // put data[] integers into equivalent Integer* objects in HEAP
    for (int inx = 0; inx < 9; ++inx)
    {
        nums.push_back( new Integer(data[inx]) );
    }

    // print the vector<Integer*> to stdout
    printIntegerStarVector( "TEST2: ORIG nums before UNIQUE", nums );

    // arg3 is a functor
#if 1
    // corrected version which fixes SEGMENTATION FAULT and all memory leaks reported by valgrind(1)
    // I THINK we want to use new C++11 cbegin() and cend(),since the equal_to predicate is passed "Integer *&"

//  DID NOT COMPILE
//OUT   IntegerStarVectorIterator dupPosition = ptgi::unique( const_cast<ConstIntegerStarVectorIterator>(nums.begin()), const_cast<ConstIntegerStarVectorIterator>(nums.end()), IntegerEqualByTenPointer() );

    // DID NOT COMPILE when equal_to predicate declared "Integer*& arg1, Integer*&  arg2"
//OUT   IntegerStarVectorIterator dupPosition = ptgi::unique( const_cast<nums::const_iterator>(nums.begin()), const_cast<nums::const_iterator>(nums.end()), IntegerEqualByTenPointer() );


    // okay when equal_to predicate declared "Integer* arg1, Integer*  arg2"
    IntegerStarVectorIterator dupPosition = ptgi::unique(nums.begin(), nums.end(), IntegerEqualByTenPointer() );
#else
    // BUGGY version that causes SEGMENTATION FAULT and valgrind(1) errors
    IntegerStarVectorIterator dupPosition = std::unique( nums.begin(), nums.end(), IntegerEqualByTenPointer() );
#endif

    printIntegerStarVector( "TEST2: modified nums AFTER UNIQUE", nums );
    int dupInx = dupPosition - nums.begin();
    std::cout << "INFO: dupInx=" << dupInx <<"\n";

    // delete the dup Integer* objects in the [dupPosition, end] range
    for (IntegerStarVectorIterator iter = dupPosition; iter != nums.end(); ++iter)
    {
        delete (*iter);
    }

    // shrink the vector
    // NB: the Integer* ptrs are NOT followed by vector::erase()
    nums.erase(dupPosition, nums.end());


    // print the uniques, by following the iter to the Integer* pointer
    for (IntegerStarVectorIterator iter = nums.begin(); iter != nums.end();  ++iter)
    {
        std::cout << "TEST2: uniq = " << (*iter)->getNum() << "\n";
    }

    // remove the unique objects from heap
    for (IntegerStarVectorIterator iter = nums.begin(); iter != nums.end();  ++iter)
    {
        delete (*iter);
    }

    // shrink the vector
    nums.erase(nums.begin(), nums.end());

    // the vector should now be completely empty
    assert( nums.size() == 0);
}

//@ print to stdout the string: "info_msg: num1, num2, .... numN\n"
void printIntegerStarVector( const std::string& msg, const std::vector<Integer*>& nums )
{
    std::cout << msg << ": ";
    int inx = 0;
    ConstIntegerStarVectorIterator  iter;

    // use const iterator and const range!
    // NB: cbegin() and cend() not supported until LATER (c++11)
    for (iter = nums.begin(), inx = 0; iter != nums.end(); ++iter, ++inx)
    {
        // output a comma seperator *AFTER* first
        if (inx > 0)
            std::cout << ", ";

        // call Integer::toString()
        std::cout << (*iter)->getNum();     // send int to stdout
//      std::cout << (*iter)->toString();   // also works, but is probably slower

    }

    // in conclusion, add newline
    std::cout << "\n";
}

Non capisco la logica qui. Quindi, se si dispone di un contenitore di puntatori e si desidera rimuovere i duplicati, che effetto ha sugli oggetti indicati dai puntatori? Non si verificano perdite di memoria perché è presente almeno un puntatore (e esattamente uno in questo contenitore) che li punta. Bene, bene, immagino che il tuo metodo potrebbe avere qualche merito con alcuni strani operatori sovraccarichi o strane funzioni di confronto che richiedono una considerazione speciale.
kccqzy,

Non sono sicuro se capisco il tuo punto. Prendi un semplice caso di un vettore <int *>, in cui i 4 puntatori puntano a numeri interi {1, 2. 2, 3}. È ordinato, ma dopo aver chiamato std :: unique, i 4 puntatori sono puntatori a numeri interi {1, 2, 3, 3}. Ora hai due puntatori identici a 3, quindi se chiami delete, esegue una duplicazione. MALE! In secondo luogo, nota che manca il 2 ° 2, una perdita di memoria.
Joe,

kccqzy, ecco il programma di esempio per farti capire meglio la mia risposta:
joe

@joe: Anche se dopo std::uniqueaver avuto [1, 2, 3, 2] non puoi chiamare delete su 2 in quanto ciò lascerebbe un puntatore penzolante su 2! => Semplicemente non chiamare delete sugli elementi tra newEnd = std::uniquee std::enddato che hai ancora puntatori a questi elementi in [std::begin, newEnd)!
MFH,

2
@ArneVogel: forse per valori insignificanti di "funziona bene". È piuttosto inutile invocare uniquea vector<unique_ptr<T>>, poiché l'unico valore duplicato che un vettore può contenere è nullptr.
Ben Voigt,

2

Con la libreria Ranges (disponibile in C ++ 20) puoi semplicemente usare

action::unique(vec);

Si noti che in realtà rimuove gli elementi duplicati, non solo spostarli.


1

Informazioni sui benchmark alexK7. Li ho provati e ho ottenuto risultati simili, ma quando l'intervallo di valori è 1 milione i casi che usano std :: sort (f1) e usando std :: unordered_set (f5) producono un tempo simile. Quando l'intervallo di valori è 10 milioni f1 è più veloce di f5.

Se l'intervallo di valori è limitato e i valori non sono firmati int, è possibile utilizzare std :: vector, la cui dimensione corrisponde all'intervallo specificato. Ecco il codice:

void DeleteDuplicates_vector_bool(std::vector<unsigned>& v, unsigned range_size)
{
    std::vector<bool> v1(range_size);
    for (auto& x: v)
    {
       v1[x] = true;    
    }
    v.clear();

    unsigned count = 0;
    for (auto& x: v1)
    {
        if (x)
        {
            v.push_back(count);
        }
        ++count;
    }
}

1

sort (v.begin (), v.end ()), v.erase (unique (v.begin (), v, end ()), v.end ());


1

Se stai cercando prestazioni e utilizzo std::vector, ti consiglio quello che fornisce questo link alla documentazione .

std::vector<int> myvector{10,20,20,20,30,30,20,20,10};             // 10 20 20 20 30 30 20 20 10
std::sort(myvector.begin(), myvector.end() );
const auto& it = std::unique (myvector.begin(), myvector.end());   // 10 20 30 ?  ?  ?  ?  ?  ?
                                                                   //          ^
myvector.resize( std::distance(myvector.begin(),it) ); // 10 20 30

cplusplus.com non è in alcun modo documentazione ufficiale.
Ilya Popov,

0
std::set<int> s;
std::for_each(v.cbegin(), v.cend(), [&s](int val){s.insert(val);});
v.clear();
std::copy(s.cbegin(), s.cend(), v.cbegin());

1
forse ridimensionare il vettore dopo averlo cancellato in modo che vi sia solo 1 allocazione di memoria durante la creazione del vettore. Forse preferisci std :: move invece di std :: copy per spostare gli ints nel vettore invece di copiarli poiché il set non sarà necessario in seguito.
Young John

0

Se non si desidera modificare il vettore (cancella, ordina), è possibile utilizzare la libreria Newton . Nella libreria secondaria dell'algoritmo è presente una chiamata di funzione, copy_single

template <class INPUT_ITERATOR, typename T>
    void copy_single( INPUT_ITERATOR first, INPUT_ITERATOR last, std::vector<T> &v )

così puoi:

std::vector<TYPE> copy; // empty vector
newton::copy_single(first, last, copy);

dove copia è il vettore in cui si desidera eseguire il push_back della copia degli elementi univoci. ma ricorda di spingere indietro gli elementi e di non creare un nuovo vettore

comunque, questo è più veloce perché non cancelli () gli elementi (che richiede molto tempo, tranne quando si pop_back (), a causa della riassegnazione)

Faccio alcuni esperimenti ed è più veloce.

Inoltre, puoi usare:

std::vector<TYPE> copy; // empty vector
newton::copy_single(first, last, copy);
original = copy;

a volte è ancora più veloce.


1
Questa funzione è presente nella libreria standard come unique_copy.
LF

0

Codice più comprensibile da: https://en.cppreference.com/w/cpp/algorithm/unique

#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <cctype>

int main() 
{
    // remove duplicate elements
    std::vector<int> v{1,2,3,1,2,3,3,4,5,4,5,6,7};
    std::sort(v.begin(), v.end()); // 1 1 2 2 3 3 3 4 4 5 5 6 7 
    auto last = std::unique(v.begin(), v.end());
    // v now holds {1 2 3 4 5 6 7 x x x x x x}, where 'x' is indeterminate
    v.erase(last, v.end()); 
    for (int i : v)
      std::cout << i << " ";
    std::cout << "\n";
}

ouput:

1 2 3 4 5 6 7

0
void removeDuplicates(std::vector<int>& arr) {
    for (int i = 0; i < arr.size(); i++)
    {
        for (int j = i + 1; j < arr.size(); j++)
        {
            if (arr[i] > arr[j])
            {
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
    }
    std::vector<int> y;
    int x = arr[0];
    int i = 0;
    while (i < arr.size())
    {
        if (x != arr[i])
        {
            y.push_back(x);
            x = arr[i];
        }
        i++;
        if (i == arr.size())
            y.push_back(arr[i - 1]);
    }
    arr = y;
}

2
Benvenuto in StackOverflow! Si prega di modificare la tua domanda per aggiungere una spiegazione di come si lavori di codice, e il motivo per cui esso è equivalente o migliore rispetto alle altre risposte. Questa domanda ha più di dieci anni e ha già molte risposte valide e ben spiegate. Senza una tua spiegazione, non è così utile e ha buone probabilità di essere sottratto o rimosso.
Das_Geek,

-1

Ecco l'esempio del problema di eliminazione duplicata che si verifica con std :: unique (). Su una macchina LINUX, il programma si arresta in modo anomalo. Leggi i commenti per i dettagli.

// Main10.cpp
//
// Illustration of duplicate delete and memory leak in a vector<int*> after calling std::unique.
// On a LINUX machine, it crashes the progam because of the duplicate delete.
//
// INPUT : {1, 2, 2, 3}
// OUTPUT: {1, 2, 3, 3}
//
// The two 3's are actually pointers to the same 3 integer in the HEAP, which is BAD
// because if you delete both int* pointers, you are deleting the same memory
// location twice.
//
//
// Never mind the fact that we ignore the "dupPosition" returned by std::unique(),
// but in any sensible program that "cleans up after istelf" you want to call deletex
// on all int* poitners to avoid memory leaks.
//
//
// NOW IF you replace std::unique() with ptgi::unique(), all of the the problems disappear.
// Why? Because ptgi:unique merely reshuffles the data:
// OUTPUT: {1, 2, 3, 2}
// The ptgi:unique has swapped the last two elements, so all of the original elements in
// the INPUT are STILL in the OUTPUT.
//
// 130215   dbednar@ptgi.com
//============================================================================

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

#include "ptgi_unique.hpp"

// functor used by std::unique to remove adjacent elts from vector<int*>
struct EqualToVectorOfIntegerStar: public std::equal_to<int *>
{
    bool operator() (const int* arg1, const int* arg2) const
    {
        return (*arg1 == *arg2);
    }
};

void printVector( const std::string& msg, const std::vector<int*>& vnums);

int main()
{
    int inums [] = { 1, 2, 2, 3 };
    std::vector<int*> vnums;

    // convert C array into vector of pointers to integers
    for (size_t inx = 0; inx < 4; ++ inx)
        vnums.push_back( new int(inums[inx]) );

    printVector("BEFORE UNIQ", vnums);

    // INPUT : 1, 2A, 2B, 3
    std::unique( vnums.begin(), vnums.end(), EqualToVectorOfIntegerStar() );
    // OUTPUT: 1, 2A, 3, 3 }
    printVector("AFTER  UNIQ", vnums);

    // now we delete 3 twice, and we have a memory leak because 2B is not deleted.
    for (size_t inx = 0; inx < vnums.size(); ++inx)
    {
        delete(vnums[inx]);
    }
}

// print a line of the form "msg: 1,2,3,..,5,6,7\n", where 1..7 are the numbers in vnums vector
// PS: you may pass "hello world" (const char *) because of implicit (automatic) conversion
// from "const char *" to std::string conversion.

void printVector( const std::string& msg, const std::vector<int*>& vnums)
{
    std::cout << msg << ": ";

    for (size_t inx = 0; inx < vnums.size(); ++inx)
    {
        // insert comma separator before current elt, but ONLY after first elt
        if (inx > 0)
            std::cout << ",";
        std::cout << *vnums[inx];

    }
    std::cout << "\n";
}

PS: ho anche eseguito "valgrind ./Main10" e valgrind non ha riscontrato problemi. Consiglio vivamente a tutti i programmatori C ++ che utilizzano LINUX di utilizzare questo strumento molto produttivo, specialmente se si stanno scrivendo applicazioni in tempo reale che devono essere eseguite 24x7 e mai perdite o crash!
Joe,

Il cuore del problema con std :: unique può essere riassunto da questa affermazione "std :: unique restituisce duplicati in stato non specificato" !!!!! Perché il comitato per le norme ha fatto questo, non lo saprò mai. Membri del Comitato .. eventuali commenti ???
Joe,

1
Sì, "std :: unique restituisce duplicati in stato non specificato". Quindi, semplicemente non fare affidamento su un array che è stato "unificato" per gestire manualmente la memoria! Il modo più semplice per farlo è usare std :: unique_ptr invece di puntatori non elaborati.
alexk7,

Questa sembra essere una risposta a una risposta diversa; non risponde alla domanda (in cui vectorcontiene numeri interi, non puntatori e non specifica un comparatore).
Toby Speight,

-2
void EraseVectorRepeats(vector <int> & v){ 
TOP:for(int y=0; y<v.size();++y){
        for(int z=0; z<v.size();++z){
            if(y==z){ //This if statement makes sure the number that it is on is not erased-just skipped-in order to keep only one copy of a repeated number
                continue;}
            if(v[y]==v[z]){
                v.erase(v.begin()+z); //whenever a number is erased the function goes back to start of the first loop because the size of the vector changes
            goto TOP;}}}}

Questa è una funzione che ho creato che puoi usare per eliminare le ripetizioni. I file di intestazione necessari sono giusti <iostream>e <vector>.

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.