Kopyaları silmenin ve bir vektörü sıralamanın en etkili yolu nedir?


274

Potansiyel olarak çok sayıda öğe ile bir C ++ vektör almak, yinelenenleri silmek ve sıralamak gerekir.

Şu anda aşağıdaki kodu var, ama çalışmıyor.

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

Bunu nasıl doğru bir şekilde yapabilirim?

Ayrıca, önce kopyaları silmek (yukarıdaki kodlamaya benzer) veya önce sıralamayı gerçekleştirmek daha hızlı mıdır? İlk önce sıralamayı yaparsam std::unique, yürütüldükten sonra sıralı kalması garanti edilir mi?

Yoksa tüm bunları yapmanın başka (belki daha verimli) bir yolu var mı?


3
Ben ilk etapta dupes önlemek için ekleme önce kontrol seçeneği var sanırım?
Joe

Doğru. Bu ideal olurdu.
Kyle Ryan

29
Yukarıdaki kodu düzeltmenizi öneririm, ya da gerçekten YANLIŞ olduğunu gösterir. std :: unique aralığın önceden sıralandığını varsayar.
Matthieu

Yanıtlar:


584

R. Pate ve Todd Gardner ile aynı fikirdeyim ; Bir std::setburada iyi bir fikir olabilir. Vektörleri kullanmaya devam etseniz bile, yeterli sayıda yinelemeniz varsa, kirli işi yapmak için bir set oluşturmaktan daha iyi olabilirsiniz.

Üç yaklaşımı karşılaştıralım:

Sadece vektörü kullanarak, sırala + benzersiz

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

Sete dönüştür (manuel)

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

Sete dönüştür (bir kurucu kullanarak)

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

Kopya sayısı değiştikçe bunların performansı şu şekildedir:

vektör ve küme yaklaşımlarının karşılaştırılması

Özet : yinelenenlerin sayısı yeterince büyük olduğunda, bir kümeye dönüştürmek ve ardından verileri bir vektöre geri dökmek daha hızlıdır .

Ve bir nedenden dolayı, set dönüşümünü manuel olarak yapmak, set yapıcısını kullanmaktan daha hızlı görünüyor - en azından kullandığım oyuncak rastgele verilerde.


61
Yapıcı yaklaşımının manuel olarak ölçülebilir bir şekilde daha kötü olduğu için şok oldum. Bunu biraz sabit bir ek yükten ayrı olarak, sadece manuel bir şey yapardı. Herkes bunu açıklayabilir mi?
Ari 5: 51

17
Harika, grafik için teşekkürler. Kopya Sayısı için birimlerin ne olduğu hakkında bir fikir verebilir misiniz? (yani, "yeterince büyük" ne kadar civarında)?
Kyle Ryan

5
@Kyle: Oldukça büyük. Bu grafik için 1 ile 1000, 100 ve 10 arasında rasgele çizilmiş tam sayılardan oluşan 1000 veri kümesi kullandım.
Nate Kohl

5
Bence sonuçlarınız yanlış. Testlerimde daha fazla yinelenen elemanlar daha hızlı vektör (karşılaştırmalı), aslında tam tersi şekilde ölçeklenir. Optimizasyonları açık ve çalışma zamanı denetimleri derlediniz mi? Benim tarafımda vektör her zaman daha hızlı, kopya sayısına bağlı olarak 100x'e kadar. VS2013, cl / Ox -D_SECURE_SCL = 0.
davidnr

39
X ekseninin tanımı eksik görünüyor.
BartoszKP

72

Nate Kohl'un profilini yeniden düzenledim ve farklı sonuçlar aldım. Test durumum için, vektörü doğrudan sıralamak her zaman bir küme kullanmaktan daha etkilidir. A kullanarak daha verimli yeni bir yöntem ekledim unordered_set.

unordered_setYöntemin yalnızca benzersiz ve sıralanmış ihtiyacınız olan tür için iyi bir sağlama işleviniz varsa işe yaradığını unutmayın . Ints için bu kolay! (Standart kütüphane, kimlik işlevi olan varsayılan bir karma sağlar.) Ayrıca, unordered_set, de, unordered olduğundan, sonunda sıralamayı unutmayın :)

Ben setve unordered_setuygulama içinde bazı kazma yaptım ve yapıcı aslında (gerçekten en azından Visual Studio uygulamasında) eklenmesini belirlemek için değerini kontrol etmeden önce her öğe için yeni bir düğüm inşa keşfetti.

İşte 5 yöntem:

f1: Sadece kullanarak vector, sort+unique

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

f2: Dönüştür set(yapıcı kullanarak)

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

f3: Dönüştür set(manuel olarak)

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

f4: Dönüştür unordered_set(bir kurucu kullanarak)

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

f5: Dönüştür unordered_set(manuel olarak)

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

Testi, [1,10], [1,1000] ve [1,100000] aralıklarında rasgele seçilen 100.000.000 int'luk bir vektörle yaptım

Sonuçlar (saniyeler içinde daha küçük daha iyidir):

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
Tamsayılar için, std :: sort'ten çok daha hızlı olan radix sıralama kullanabilirsiniz.
Changming Sun

2
Hızlı ipucu, kullanmak sortveya uniqueyöntemler, yapmanız gereken#include <algorithm>
Davmrtl

3
@ChangmingSun Optimizer'ın neden f4'te başarısız olduğunu düşündüm? Sayılar f5'ten önemli ölçüde farklıdır. Bana bir anlam ifade etmiyor.
sandthorn

1
@sandthorn Cevabımda açıklandığı gibi, uygulama girdi dizisindeki her öğe için bir düğüm (dinamik ayırma dahil) oluşturur, bu da yinelenen her değer için israftır. Optimize edicinin bunu atlayabileceğini bilmesinin hiçbir yolu yoktur.
alexk7

Ah, bu yaklaşık Scott Meyer'in görüşmelerin birinde hatırlatıyor CWUK scenerio yavaşlatmak için propablities bir doğası vardır emplaceinşaatın tür.
sandthorn

58

std::unique yinelenen öğeleri yalnızca komşu olmaları durumunda kaldırır: vektörü istediğiniz gibi çalışmaya başlamadan önce sıralamanız gerekir.

std::unique kararlı olduğu için tanımlanır, bu nedenle vektör, benzersiz çalıştıktan sonra da sıralanır.


42

Bunu ne için kullandığınızdan emin değilim, bu yüzden bunu% 100 kesinlik ile söyleyemem, ama normalde "sıralı, benzersiz" kapsayıcı düşündüğümde, bir std :: set düşünüyorum . Usecase'iniz için daha uygun olabilir:

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

Aksi takdirde, benzersiz çağırmadan önce sıralama (diğer cevapların işaret ettiği gibi) gitmenin yoludur.


Konuya gelelim! std :: set, sıralı benzersiz bir set olarak belirtilir. Çoğu uygulama, verimli bir sıralı ikili ağaç veya benzeri bir şey kullanır.
notnoop

+1 Set düşüncesi de. Bu cevabı çoğaltmak istemedim
Tom

Std :: set'in sıralanacağı garanti ediliyor mu? Pratikte olması mantıklıdır, ancak standart bunu gerektirir mi?
MadCoder

1
Evet, 23.1.4.9 bakınız "birleştirici kapların Yineleyicilerin temel özelliğidir dışı inen bunları oluşturmak için kullanılmıştır karşılaştırma ile tanımlanır şifreler olmayan azalan sırayla konteyner aracılığıyla da" döngü
Todd Gardner

1
@MadCoder: Bir kümenin sıralanan şekilde uygulandığı "mantıklı" değildir. Ayrıca sıralanmamış karma tablolar kullanılarak uygulanan kümeler vardır. Aslında, çoğu insan mevcut olduğunda karma tabloları kullanmayı tercih eder. Ancak C ++ 'daki adlandırma kuralı, sıralanan ilişkilendirilebilir kapsayıcılara basitçe "set" / "map" (Java'daki TreeSet / TreeMap'e benzer) adı verilir; ve standart dışında kalan karma ilişkilendirilebilir kaplara "hash_set" / "hash_map" (SGI STL) veya "unordered_set" / "unordered_map" (TR1) (Java'da HashSet ve
HashMap'a

22

std::uniqueyalnızca yinelenen öğelerin ardışık çalıştırılmasında çalışır, bu yüzden önce sıralamanız daha iyi olur. Bununla birlikte, kararlıdır, bu nedenle vektörünüz sıralanmış olarak kalır.


18

İşte sizin için yapacak bir şablon:

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

şöyle çağır:

removeDuplicates<int>(vectorname);

2
+1 Uzaklaştır! - ancak şablon argümanlarını açıkça belirtmeden removeDuplicates (vec) yazabilirsiniz
Faisal Vali

10
Ya da daha iyisi, doğrudan şablon yineleyicileri almasını (başlayıp bitmesini) sağlayın ve bir vektörün yanı sıra diğer yapılarda da çalıştırabilirsiniz.
Kyle Ryan

Cehennem evet, şablonlar! küçük listeler için hızlı düzeltme, tam STL tarzı. +1 thx
QuantumKarl

@Kyle - sadece erase()yöntemi olan diğer kaplarda , aksi takdirde yeni son yineleyiciyi döndürmeniz ve arama kodunun kapsayıcıyı kesmesi gerekir.
Toby Speight

8

Verimlilik karmaşık bir kavramdır. Zaman ve mekan ile ilgili hususların yanı sıra genel ölçümler (yalnızca O (n) gibi belirsiz cevaplar aldığınızda) ve spesifik olanlar (örn. Giriş özelliklerine bağlı olarak kabarcık sıralaması hızlı sıralamadan çok daha hızlı olabilir) vardır.

Nispeten az sayıda yinelemeniz varsa, sıralamadan sonra benzersiz ve silme işlemi gitmek için bir yol gibi görünüyor. Nispeten çok sayıda yinelemeniz varsa, vektörden bir set oluşturmak ve ağır kaldırma yapmasına izin vermek onu kolayca yenebilir.

Sadece zaman verimliliğine de odaklanmayın. Sıralama + benzersiz + silme işlemi O (1) alanında, ayarlanan yapı ise O (n) alanında çalışır. Ve ikisi de doğrudan harita azaltımlı bir paralelleştirmeye borç vermiyor (gerçekten çok büyük veri kümeleri için).


Size harita / azaltma yeteneği ne kazandırır? Düşünebildiğim tek şey dağıtılmış bir birleştirme sıralamasıdır ve yine de son birleştirme işleminde yalnızca bir iş parçacığı kullanabilirsiniz.
Zan Lynx

1
Evet, bir kontrol düğümü / iş parçacığı olması gerekir. Bununla birlikte, sorunu, üst / üst iş parçacığının ele aldığı işçi / alt iş parçacığı sayısına ve her bir yaprak düğümünün işlemesi gereken veri kümesinin boyutuna üst sınırlar koymak için gerektiği kadar bölebilirsiniz. Harita azaltma ile tüm problemler kolayca çözülemez, sadece 10 terabaytlık veri ile uğraşmanın "Salı" olarak adlandırıldığı benzer (yüzeyde, yine de) optimizasyon sorunları ile ilgilenen insanlar olduğunu belirtmek istedim.

7

Aramadan önce sıralamanız gerekir, uniqueçünkü uniqueyalnızca yan yana yinelenenleri kaldırır.

düzenleme: 38 saniye ...


7

uniqueyalnızca ardışık yinelenen öğeleri kaldırır (bu, doğrusal zamanda çalışması için gereklidir), bu nedenle önce sıralamayı gerçekleştirmelisiniz. Çağrısından sonra sıralı olarak kalacaktır unique.


7

Elemanların sırasını değiştirmek istemiyorsanız, bu çözümü deneyebilirsiniz:

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

Belki de set yerine unordered_set kullanın (ve eğer varsa boost :: remove_erase_if)
gast128

4

Varsayarak bir bir vektördür kullanılarak bitişik çiftleri kaldırma

a.erase(unique(a.begin(),a.end()),a.end());O (n) zamanında çalışır .


1
bitişik kopyalar. tamam, bu yüzden bir std::sortilk gerekiyor .
v.oddou

2

Daha önce de belirtildiği gibi, uniquesıralanmış bir kapsayıcı gerektirir. Ayrıca, uniqueaslında kaptan öğeleri kaldırmaz. Bunun yerine, sonuna kadar kopyalanırlar, bu uniquetür ilk yinelenen öğeye işaret eden bir yineleyici döndürürler eraseve öğeleri gerçekten kaldırmak için çağırmanız beklenir .


Benzersiz sıralı bir kapsayıcı gerektirir mi, yoksa yalnızca bitişik kopyalar içermeyecek şekilde yalnızca giriş sırasını yeniden mi düzenler? Ben ikincisini düşündüm.

@Pate, haklısın. Bir tane gerektirmez. Bitişik yinelemeleri kaldırır.
Bill Lynch

Çoğaltılmış olabilecek bir kapsayıcı varsa ve kapsayıcıda herhangi bir yerde çoğaltılmış değeri olmayan bir kapsayıcı istiyorsanız, önce kapsayıcıyı sıralamalı, sonra benzersiz olana aktarmalı ve daha sonra çoğaltmaları gerçekten kaldırmak için silme işlemini kullanmalısınız. . Sadece bitişik kopyaları kaldırmak istiyorsanız, kapsayıcıyı sıralamanız gerekmez. Ancak yinelenen değerlerle sonuçlanacaksınız: 1 2 2 3 2 4 2 5 2, sıralama olmadan benzersiz olarak aktarılırsa 1 2 3 2 4 2 5 2 olarak değiştirilir, sıralanırsa, benzersiz ve silinirse 1 2 3 4 5 olarak değiştirilir .
Max Lybbert

2

Nate Kohl tarafından önerilen ve sadece vektör, sort + unique kullanarak standart yaklaşım:

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

bir işaretçi vektörü için çalışmaz.

Cplusplus.com adresindeki bu örneğe dikkatlice bakın .

Örneklerinde, sonuna kadar taşınan "kopyalar" aslında? (tanımlanmamış değerler), çünkü "yinelenen adlar" olarak adlandırılanlar SOMETIMES "ekstra öğeler" ve SOMETIMES orijinal vektörde "eksik öğeler" vardır.

std::unique()Nesnelere işaretçiler vektörü kullanıldığında bir sorun oluşur (bellek sızıntıları, HEAP'ten verilerin kötü okunması, bölünmüş hatalara neden olan yinelenen serbest bırakmalar, vb.).

Soruna benim çözüm: İşte std::unique()ile değiştirin ptgi::unique().

Aşağıdaki ptgi_unique.hpp dosyasına bakın:

// 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

Ve test etmek için kullandığım UNIT Test programı:

// 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";
}

Buradaki mantığı anlamıyorum. Bir işaretçi kabınız varsa ve kopyaları kaldırmak istiyorsanız, bu işaretçilerin işaret ettiği nesneleri nasıl etkiler? Bellek sızıntısı olmaz çünkü onları işaret eden en az bir işaretçi (ve bu kapta tam olarak bir işaretçi) vardır. Pekala, yönteminizin bazı garip aşırı yüklenmiş operatörlerle veya özel dikkat gerektiren garip karşılaştırma işlevleriyle bazı değerleri olabileceğini düşünüyorum.
kccqzy

Demek istediğini anladığımdan emin değilim. 4 işaretçinin {1, 2. 2, 3} tamsayılarını gösterdiği basit bir <int *> vektörünü ele alalım. Sıralanır, ancak std :: unique'ı çağırdıktan sonra 4 işaretçi {1, 2, 3, 3} tamsayılarına işaretçilerdir. Şimdi 3'e iki özdeş işaretçiniz var, bu nedenle delete'i çağırırsanız, yinelenen bir silme işlemi yapar. KÖTÜ! İkincisi, 2. 2'nin eksik olduğunu, bir bellek sızıntısı olduğuna dikkat edin.
joe

kccqzy, cevabımı daha iyi anlamanız için örnek program heres:
joe

@joe: std::unique[1, 2, 3, 2] 'ye sahip olduktan sonra bile 2'de delete çağrısı yapamazsınız; => Sadece bu öğeler arasında hala işaretçiler var gibi newEnd = std::uniqueve arasındaki öğeler üzerinde silme std::endçağrmayın [std::begin, newEnd)!
MFH

2
@ArneVogel: Belki de "iyi çalışıyor" önemsiz değerleri için. Bu aramaya oldukça anlamsız uniquebir üzerinde vector<unique_ptr<T>>içerebilir sadece çoğaltılamaz değer böyle bir vektör olduğu gibi nullptr.
Ben Voigt

2

Ranges kütüphanesi ile (C ++ 20 ile geliyor)

action::unique(vec);

Yinelenen öğeleri kaldırdığına dikkat edin, yalnızca onları taşımakla kalmaz.


1

AlexK7 kriterleri hakkında. Onları denedim ve benzer sonuçlar aldım, ancak değer aralığı 1 milyon olduğunda std :: sort (f1) ve std :: unordered_set (f5) kullanan durumlar benzer zaman üretir. Değer aralığı 10 milyon olduğunda f1, f5'ten daha hızlıdır.

Değer aralığı sınırlıysa ve değerler imzasız int ise, boyutu verilen aralığa karşılık gelen std :: vector kullanmak mümkündür. İşte kod:

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 (benzersiz (v.begin (), v, end ()), v.end ());


1

Performans ve kullanım arıyorsanız std::vector, bu belge bağlantısının sağladığı öneriyi öneririm .

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 hiçbir şekilde resmi dokümantasyon değildir.
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
belki vektörü temizledikten sonra yeniden boyutlandırabilir, böylece vektörü oluştururken yalnızca 1 bellek tahsisi olur. Belki daha sonra sete ihtiyaç duyulmayacağından, stts :: copy yerine std :: copy yerine vektörleri taşımak yerine std :: move'ı tercih edin.
YoungJohn

0

Eğer varsa yok (sıralama, silme) vektörü değiştirmek istediğiniz sonra kullanabileceğiniz Newton kütüphane algoritması alt kütüphanesinden bir işlev çağrısı, orada, copy_single

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

Böylece yapabilirsiniz:

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

burada kopya , benzersiz öğelerin kopyasını geri itmek istediğiniz vektördür . ancak öğeleri geri ittiğinizi ve yeni bir vektör oluşturmadığınızı unutmayın

Her neyse, bu daha hızlı çünkü öğeleri silmiyor () (yeniden atama nedeniyle pop_back () dışında çok zaman alıyor)

Bazı deneyler yapıyorum ve daha hızlı.

Ayrıca, şunları kullanabilirsiniz:

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

bazen hala daha hızlı.


1
Bu işlev standart kütüphanede olarak bulunur unique_copy.
LF

0

Daha anlaşılır kod: 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";
}

çıkışına:

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
StackOverflow'a hoş geldiniz! Lütfen kodunuzu nasıl çalıştığına ve neden diğer yanıtlarla eşdeğer veya daha iyi olduğuna dair bir açıklama eklemek için sorunuzu düzenleyin . Bu soru on yıldan daha eski ve zaten çok iyi, iyi açıklanmış cevapları var. Sizinki hakkında bir açıklama olmadan, o kadar yararlı değildir ve indirgenme veya kaldırılma şansı yüksektir.
Das_Geek

-1

Aşağıda, std :: unique () ile oluşan yinelenen silme sorunu örneği verilmiştir. LINUX makinesinde program çöküyor. Ayrıntılar için yorumları okuyun.

// 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: Ben de "valgrind ./Main10" koştu ve valgrind hiçbir sorun bulunamadı. Bu çok üretken aracı kullanmak için LINUX kullanan tüm C ++ programcılarına, 24x7 çalıştırması gereken gerçek zamanlı uygulamalar yazıyorsanız ve asla sızıntı veya çökme önermemesini şiddetle tavsiye ederim!
joe

Std :: unique ile sorunun kalbi bu ifade ile özetlenebilir "std :: benzersiz belirtilmemiş durumda yinelenen döndürür" !!!!! Neden standartlar komitesi bunu yaptı, asla bilemeyeceğim. Komite üyeleri .. herhangi bir yorum ???
joe

1
Evet, "std :: unique belirtilmemiş durumdaki kopyaları döndürür". Bu nedenle, hafızayı elle yönetmek için "benzersiz" bir diziye güvenmeyin! Bunu yapmanın en basit yolu ham işaretçiler yerine std :: unique_ptr kullanmaktır.
alexk7

Bu farklı bir cevaba yanıt gibi görünüyor; soruyu cevaplamaz (içinde vectorişaretçiler değil tamsayılar içerir ve bir karşılaştırıcı belirtmez).
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;}}}}

Bu, tekrarları silmek için kullanabileceğiniz bir işlevdir. Gerekli başlık dosyaları sadece <iostream>ve <vector>.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.