Apa cara paling efisien untuk menghapus duplikat dan mengurutkan vektor?

274

Saya perlu mengambil vektor C ++ dengan elemen yang berpotensi banyak, menghapus duplikat, dan mengurutkannya.

Saat ini saya memiliki kode di bawah ini, tetapi tidak berfungsi.

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

Bagaimana saya bisa melakukan ini dengan benar?

Selain itu, apakah lebih cepat menghapus duplikat terlebih dahulu (mirip dengan kode di atas) atau melakukan pengurutan terlebih dahulu? Jika saya melakukan penyortiran terlebih dahulu, apakah dijamin akan tetap disortir setelah std::uniquedieksekusi?

Atau adakah cara lain (mungkin lebih efisien) untuk melakukan semua ini?

Kyle Ryan
sumber
3
Saya berasumsi Anda tidak memiliki opsi untuk memeriksa sebelum memasukkan untuk menghindari dupes di tempat pertama?
Joe
Benar. Itu akan ideal.
Kyle Ryan
29
Saya akan menyarankan memperbaiki kode di atas, atau benar-benar menunjukkan bahwa itu SALAH. std :: unik menganggap kisaran sudah diurutkan.
Matthieu M.

Jawaban:

584

Saya setuju dengan R. Pate dan Todd Gardner ; Sebuahstd::set mungkin ide yang baik di sini. Bahkan jika Anda terjebak menggunakan vektor, jika Anda memiliki cukup duplikat, Anda mungkin lebih baik membuat satu set untuk melakukan pekerjaan kotor.

Mari kita bandingkan tiga pendekatan:

Hanya menggunakan vektor, urutkan + unik

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

Konversi untuk mengatur (secara manual)

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

Konversi ke set (menggunakan konstruktor)

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

Begini cara kerjanya saat jumlah duplikat berubah:

perbandingan vektor dan set pendekatan

Rangkuman : ketika jumlah duplikat cukup besar, sebenarnya lebih cepat untuk mengonversi ke set dan kemudian membuang data kembali ke vektor .

Dan untuk beberapa alasan, melakukan konversi set secara manual tampaknya lebih cepat daripada menggunakan set constructor - setidaknya pada data acak mainan yang saya gunakan.

Nate Kohl
sumber
61
Saya terkejut bahwa pendekatan konstruktor secara konsisten lebih buruk daripada manual. Anda akan terpisah dari beberapa overhead konstan kecil, itu hanya akan melakukan hal manual. Adakah yang bisa menjelaskan ini?
Ari
17
Keren, terima kasih untuk grafiknya. Bisakah Anda memberi gambaran tentang berapa unit untuk Jumlah Duplikat? (yaitu, sekitar seberapa besar "cukup besar")?
Kyle Ryan
5
@Kyle: Cukup besar. Saya menggunakan kumpulan data 1.000.000 bilangan bulat yang ditarik secara acak antara 1 dan 1000, 100, dan 10 untuk grafik ini.
Nate Kohl
5
Saya pikir hasil Anda salah. Dalam pengujian saya, semakin banyak elemen yang diduplikasi, vektor yang lebih cepat (komparatif) sebenarnya adalah skala sebaliknya. Apakah Anda mengkompilasi dengan optimisasi aktif dan runtime memeriksa? Di sisi saya vektor selalu lebih cepat, hingga 100x tergantung pada jumlah duplikat. VS2013, cl / Ox -D_SECURE_SCL = 0.
davidnr
39
Deskripsi sumbu x tampaknya tidak ada.
BartoszKP
72

Saya redid profiling Nate Kohl dan mendapat hasil yang berbeda. Untuk kasus pengujian saya, secara langsung menyortir vektor selalu lebih efisien daripada menggunakan satu set. Saya menambahkan metode baru yang lebih efisien, menggunakan unordered_set.

Perlu diingat bahwa unordered_setmetode ini hanya berfungsi jika Anda memiliki fungsi hash yang baik untuk jenis yang Anda butuhkan unik dan diurutkan. Untuk int, ini mudah! (Perpustakaan standar menyediakan hash default yang hanya fungsi identitas.) Juga, jangan lupa untuk mengurutkan di akhir karena unordered_set adalah, well, unordered :)

Saya melakukan beberapa penggalian di dalam setdan unordered_setimplementasi dan menemukan bahwa konstruktor benar-benar membangun simpul baru untuk setiap elemen, sebelum memeriksa nilainya untuk menentukan apakah itu benar-benar harus dimasukkan (dalam implementasi Visual Studio, setidaknya).

Berikut adalah 5 metode:

f1: Hanya menggunakan vector, sort+unique

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

f2: Konversi ke set(menggunakan konstruktor)

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

f3: Konversi ke set(secara manual)

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

f4: Konversi ke unordered_set(menggunakan konstruktor)

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

f5: Konversi ke unordered_set(secara manual)

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

Saya melakukan tes dengan vektor 100.000.000 int yang dipilih secara acak dalam rentang [1,10], [1.1000], dan [1.100000]

Hasilnya (dalam detik, lebih kecil lebih baik):

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
alexk7
sumber
4
Untuk bilangan bulat, Anda dapat menggunakan radix sort, yang jauh lebih cepat daripada std :: sort.
Changming Sun
2
Tip cepat, untuk menggunakan sortatau uniquemetode, Anda harus#include <algorithm>
Davmrtl
3
@ ChangmingSun Saya bertanya-tanya mengapa optimizer sepertinya gagal pada f4? Jumlahnya berbeda secara dramatis dengan f5. Itu tidak masuk akal bagi saya.
sandthorn
1
@ sandthorn Seperti dijelaskan dalam jawaban saya, implementasi membangun sebuah simpul (termasuk alokasi dinamis) untuk setiap elemen dari urutan input, yang boros untuk setiap nilai yang akhirnya menjadi duplikat. Tidak ada cara pengoptimal bisa tahu itu bisa melewati itu.
alexk7
Ah, itu mengingatkan saya pada salah satu pembicaraan Scott Meyer tentang CWUK scenerio yang memiliki sifat kemungkinan untuk memperlambat emplacejenis konstruksi.
sandthorn
58

std::unique hanya menghapus elemen duplikat jika bertetangga: Anda harus mengurutkan vektor terlebih dahulu sebelum berfungsi sesuai keinginan Anda.

std::unique didefinisikan sebagai stabil, sehingga vektor akan tetap diurutkan setelah dijalankan secara unik.

jskinner
sumber
42

Saya tidak yakin untuk apa Anda menggunakan ini, jadi saya tidak bisa mengatakan ini dengan kepastian 100%, tetapi biasanya ketika saya berpikir wadah "diurutkan, unik", saya memikirkan std :: set . Ini mungkin lebih cocok untuk pengguna kami:

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

Kalau tidak, menyortir sebelum memanggil unik (seperti jawaban lain tunjukkan) adalah cara untuk pergi.

Todd Gardner
sumber
Yah to the point! std :: set ditentukan untuk menjadi set unik yang diurutkan. Sebagian besar implementasi menggunakan pohon biner yang efisien dan sejenisnya.
notnoop
+1 Pemikiran set juga. Tidak ingin menduplikasi jawaban ini
Tom
Apakah std :: set dijamin akan diurutkan? Masuk akal jika dalam praktiknya memang demikian, tetapi apakah standar mengharuskannya?
MadCoder
1
Yup, lihat 23.1.4.9 "Sifat dasar iterator dari wadah asosiatif adalah bahwa mereka iterate melalui kontainer dalam urutan kunci yang tidak menurun di mana non-turun didefinisikan oleh perbandingan yang digunakan untuk membangunnya"
Todd Gardner
1
@ MadCoder: Ini tidak selalu "masuk akal" bahwa set diimplementasikan dengan cara yang diurutkan. Ada juga set diimplementasikan menggunakan tabel hash, yang tidak diurutkan. Bahkan, kebanyakan orang lebih suka menggunakan tabel hash saat tersedia. Tetapi konvensi penamaan di C ++ kebetulan bahwa wadah asosiatif yang diurutkan diberi nama "set" / "map" (analog dengan TreeSet / TreeMap di Jawa); dan wadah asosiatif hash, yang tidak disertakan dalam standar, disebut "hash_set" / "hash_map" (SGI STL) atau "unordered_set" / "unordered_map" (TR1) (analog dengan HashSet dan HashMap di Jawa)
newacct
22

std::uniquehanya berfungsi pada menjalankan elemen duplikat berturut-turut, jadi Anda sebaiknya mengurutkannya terlebih dahulu Namun, ini stabil, sehingga vektor Anda akan tetap diurutkan.

David Seiler
sumber
18

Berikut template untuk melakukannya untuk Anda:

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

sebut saja seperti:

removeDuplicates<int>(vectorname);
DShook
sumber
2
+1 Templatize away! - tetapi Anda hanya dapat menulis removeDuplicates (vec), tanpa secara eksplisit menentukan argumen templat
Faisal Vali
10
Atau bahkan lebih baik, cukup ambil iterator templated secara langsung (awal dan akhir), dan Anda dapat menjalankannya pada struktur lain selain vektor.
Kyle Ryan
Ya ampun, templat! perbaikan cepat untuk daftar kecil, gaya STL penuh. +1 thx
QuantumKarl
@Kyle - hanya pada wadah lain yang memiliki erase()metode, kalau tidak Anda harus mengembalikan iterator akhir yang baru dan meminta kode panggilan memotong wadah.
Toby Speight
8

Efisiensi adalah konsep yang rumit. Ada pertimbangan waktu vs. ruang, serta pengukuran umum (di mana Anda hanya mendapatkan jawaban yang tidak jelas seperti O (n)) vs. yang spesifik (mis. Gelembung sort bisa jauh lebih cepat daripada quicksort, tergantung pada karakteristik input).

Jika Anda memiliki duplikat yang relatif sedikit, maka pengurutan diikuti oleh unik dan menghapus tampaknya cara untuk pergi. Jika Anda memiliki duplikat yang relatif banyak, membuat satu set dari vektor dan membiarkannya melakukan pengangkatan berat dapat dengan mudah mengalahkannya.

Jangan hanya berkonsentrasi pada efisiensi waktu juga. Sortir + unik + hapus beroperasi di ruang O (1), sedangkan konstruksi yang ditetapkan beroperasi di ruang O (n). Dan tidak ada yang secara langsung cocok untuk paralelisasi pengurangan peta (untuk dataset yang sangat besar ).


sumber
Apa yang akan memberi Anda peta / kurangi kemampuan? Satu-satunya yang bisa saya pikirkan adalah jenis gabungan terdistribusi dan Anda masih bisa hanya menggunakan satu utas di penggabungan akhir.
Zan Lynx
1
Ya, Anda harus memiliki satu simpul / utas pengontrol. Namun, Anda dapat membagi masalah sebanyak yang diperlukan untuk menempatkan batas atas pada jumlah thread pekerja / anak yang ditangani oleh thread pengontrol / induk, dan pada ukuran dataset yang harus diproses oleh setiap simpul daun. Tidak semua masalah mudah dipecahkan dengan pengurangan peta, saya hanya ingin menunjukkan ada orang yang menangani masalah optimasi yang serupa (di permukaan, tetap), di mana berurusan dengan data 10 terabyte disebut "Selasa".
7

Anda perlu mengurutkannya sebelum menelepon uniquekarenaunique hanya menghapus duplikat yang bersebelahan.

edit: 38 detik ...

David Johnstone
sumber
7

uniquehanya menghapus elemen duplikat berurutan (yang diperlukan untuk menjalankannya dalam waktu linier), jadi Anda harus melakukan pengurutan terlebih dahulu. Itu akan tetap diurutkan setelah panggilan ke unique.

Peter
sumber
7

Jika Anda tidak ingin mengubah urutan elemen, maka Anda dapat mencoba solusi ini:

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());
}
Yury
sumber
Mungkin menggunakan unordered_set sebagai ganti set (dan tingkatkan :: remove_erase_if jika tersedia)
gast128
4

Dengan asumsi bahwa a adalah vektor, hapus duplikat yang berdekatan menggunakan

a.erase(unique(a.begin(),a.end()),a.end());berjalan dalam waktu O (n) .

KPMG
sumber
1
duplikat yang berdekatan. ok, jadi perlu yang std::sortpertama.
v.oddou
2

Seperti yang sudah disebutkan, uniquemembutuhkan wadah yang disortir. Selain itu, uniquesebenarnya tidak menghapus elemen dari wadah. Sebagai gantinya, mereka disalin hingga akhir, uniquemengembalikan iterator yang menunjuk ke elemen duplikat pertama seperti itu, dan Anda diharapkan menelepon eraseuntuk benar-benar menghapus elemen-elemen tersebut.

Max Lybbert
sumber
Apakah unik memerlukan wadah yang diurutkan, atau apakah hanya mengatur ulang urutan input sehingga tidak mengandung duplikat yang berdekatan? Saya pikir yang terakhir.
@Pate, Anda benar. Itu tidak memerlukan satu. Ini menghapus duplikat yang berdekatan.
Bill Lynch
Jika Anda memiliki wadah yang mungkin memiliki duplikat, dan Anda ingin wadah yang tidak memiliki nilai duplikat di mana pun di dalam wadah maka Anda harus terlebih dahulu mengurutkan wadah, lalu meneruskannya ke unik, dan kemudian gunakan hapus untuk benar-benar menghapus duplikat . Jika Anda hanya ingin menghapus duplikat yang berdekatan, maka Anda tidak perlu menyortir wadah. Tetapi Anda akan berakhir dengan nilai duplikat: 1 2 2 3 2 4 2 5 2 akan diubah menjadi 1 2 3 2 4 2 5 2 jika diteruskan ke unik tanpa pengurutan, 1 2 3 4 5 jika diurutkan, diteruskan ke unik dan dihapus .
Max Lybbert
2

Pendekatan standar yang disarankan oleh Nate Kohl, hanya menggunakan vektor, urutkan + unik:

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

tidak berfungsi untuk vektor pointer.

Perhatikan baik-baik contoh ini di cplusplus.com .

Dalam contoh mereka, "duplikat yang disebut" dipindahkan ke akhir sebenarnya ditampilkan sebagai? (nilai yang tidak ditentukan), karena "duplikat yang disebut" adalah SOMETIM "elemen tambahan" dan SOMETIM ada "elemen yang hilang" yang ada di vektor asli.

Terjadi masalah saat menggunakan std::unique() pada vektor pointer ke objek (kebocoran memori, buruk membaca data dari HEAP, duplikat membebaskan, yang menyebabkan kesalahan segmentasi, dll).

Inilah solusi saya untuk masalah ini: ganti std::unique()denganptgi::unique() .

Lihat file ptgi_unique.hpp di bawah ini:

// 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 [email protected] 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

Dan inilah program UNIT Test yang saya gunakan untuk mengujinya:

// 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 [email protected] 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";
}
joe
sumber
Saya tidak mengerti alasannya di sini. Jadi, jika Anda memiliki wadah pointer, dan Anda ingin menghapus duplikat, bagaimana hal itu mempengaruhi objek yang ditunjuk oleh pointer? Tidak ada kebocoran memori yang akan terjadi karena setidaknya ada satu penunjuk (dan tepat satu dalam wadah ini) yang menunjuk ke mereka. Well, well, saya kira metode Anda mungkin memiliki beberapa kelebihan dengan beberapa operator kelebihan beban aneh atau fungsi perbandingan aneh yang memang memerlukan pertimbangan khusus.
kccqzy
Tidak yakin apakah saya mengerti maksud Anda. Ambil contoh sederhana dari vektor <int *>, di mana 4 pointer menunjuk ke bilangan bulat {1, 2. 2, 3}. Diurutkan, tetapi setelah Anda memanggil std :: unik, 4 pointer adalah pointer ke integer {1, 2, 3, 3}. Sekarang Anda memiliki dua pointer identik ke 3, jadi jika Anda memanggil delete, itu menghapus duplikat. BURUK! Kedua, perhatikan bahwa 2 yang kedua hilang, memori bocor.
joe
kccqzy, inilah program contoh bagi Anda untuk memahami jawaban saya lebih baik:
joe
@ Jo: Meskipun jika setelah std::unique[1, 2, 3, 2] Anda tidak dapat memanggil delete pada 2 karena itu akan meninggalkan pointer menggantung ke 2! => Cukup jangan panggil delete pada elemen di antara newEnd = std::uniquedan std::endkarena Anda masih memiliki petunjuk untuk elemen-elemen ini di dalam [std::begin, newEnd)!
MFH
2
@ ArneVogel: Untuk nilai sepele dari "berfungsi dengan baik", mungkin. Ini agak sia-sia untuk memanggil uniquepada vector<unique_ptr<T>>, sebagai satu-satunya digandakan nilai seperti vektor dapat berisi adalah nullptr.
Ben Voigt
2

Dengan perpustakaan Ranges (datang dalam C ++ 20), Anda cukup menggunakan

action::unique(vec);

Perhatikan bahwa sebenarnya menghapus elemen duplikat, bukan hanya memindahkannya.

LF
sumber
1

Tentang tolok ukur alexK7. Saya mencobanya dan mendapatkan hasil yang serupa, tetapi ketika kisaran nilainya 1 juta, case menggunakan std :: sort (f1) dan menggunakan std :: unordered_set (f5) menghasilkan waktu yang sama. Ketika rentang nilai 10 juta f1 lebih cepat dari f5.

Jika rentang nilai terbatas dan nilainya tidak bertanda tangan, dimungkinkan untuk menggunakan std :: vector, ukurannya sesuai dengan rentang yang diberikan. Ini kodenya:

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;
    }
}
Mikhail Semenov
sumber
1

sortir (v.begin (), v.end ()), v.erase (unik (v.begin (), v, end ()), v.end ());

Yohanna
sumber
1

Jika Anda mencari kinerja dan penggunaan std::vector, saya sarankan yang disediakan oleh tautan dokumentasi ini .

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
Gines Hidalgo
sumber
cplusplus.com sama sekali bukan dokumentasi resmi.
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());
Wes
sumber
1
mungkin mengubah ukuran vektor setelah membersihkannya sehingga hanya ada 1 alokasi memori saat membangun vektor. Mungkin lebih suka std :: move daripada std :: copy untuk memindahkan ints ke dalam vektor daripada menyalinnya karena set tidak akan diperlukan nanti.
YoungJohn
0

Jika Anda tidak ingin memodifikasi vektor (hapus, urutkan) maka Anda dapat menggunakan perpustakaan Newton , Di sublibrary algoritma ada panggilan fungsi, copy_single

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

jadi kamu bisa:

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

di mana salinan adalah vektor di mana Anda ingin push_back salinan elemen unik. tapi ingat Anda push_back elemen, dan Anda tidak membuat vektor baru

lagi pula, ini lebih cepat karena Anda tidak menghapus () elemen (yang membutuhkan banyak waktu, kecuali ketika Anda pop_back (), karena penugasan kembali)

Saya membuat beberapa percobaan dan lebih cepat.

Anda juga dapat menggunakan:

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

terkadang masih lebih cepat.

Moises Rojo
sumber
1
Fungsi ini hadir di perpustakaan standar sebagai unique_copy.
LF
0

Kode lebih mudah dimengerti dari: 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
Jayhello
sumber
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;
}
robertlucian13
sumber
2
Selamat datang di StackOverflow! Harap edit pertanyaan Anda untuk menambahkan penjelasan tentang cara kerja kode Anda, dan mengapa itu setara atau lebih baik daripada jawaban lainnya. Pertanyaan ini sudah berusia lebih dari sepuluh tahun , dan sudah memiliki banyak jawaban yang bagus dan jelas. Tanpa penjelasan tentang Anda, itu tidak berguna dan memiliki peluang bagus untuk diturunkan atau dihapus.
Das_Geek
-1

Inilah contoh masalah penghapusan duplikat yang terjadi dengan std :: unique (). Pada mesin LINUX, program macet. Baca komentar untuk detailnya.

// 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   [email protected]
//============================================================================

#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";
}
joe
sumber
PS: Saya juga menjalankan "valgrind ./Main10", dan valgrind tidak menemukan masalah. Saya SANGAT merekomendasikan semua programmer C ++ menggunakan LINUX untuk menggunakan alat yang sangat produktif ini, terutama jika Anda menulis aplikasi real-time yang harus menjalankan 24x7, dan tidak pernah bocor atau macet!
joe
Inti dari masalah dengan std :: unik dapat diringkas dengan pernyataan ini "std :: unik mengembalikan duplikat dalam keadaan tidak ditentukan" !!!!! Mengapa komite standar melakukan ini, saya tidak akan pernah tahu. Anggota komite .. ada komentar ???
joe
1
Ya, "std :: unik mengembalikan duplikat dalam keadaan tidak ditentukan". Jadi, jangan mengandalkan array yang telah "unik" untuk mengelola memori secara manual! Cara paling sederhana untuk melakukan ini adalah dengan menggunakan std :: unique_ptr bukan pointer mentah.
alexk7
Ini tampaknya merupakan respons terhadap jawaban yang berbeda; itu tidak menjawab pertanyaan (di mana vectorberisi bilangan bulat, bukan pointer, dan tidak menentukan pembanding).
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;}}}}

Ini adalah fungsi yang saya buat yang dapat Anda gunakan untuk menghapus pengulangan. File header yang dibutuhkan hanya <iostream>dan <vector>.

Grab
sumber