【问题标题】:What's the most efficient way to erase duplicates and sort a vector?删除重复项和对向量进行排序的最有效方法是什么?
【发布时间】:2010-11-05 17:04:30
【问题描述】:

我需要获取一个可能包含很多元素的 C++ 向量,删除重复项并对其进行排序。

我目前有以下代码,但它不起作用。

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

我怎样才能正确地做到这一点?

此外,先删除重复项(类似于上面的代码)还是先执行排序更快?如果我先执行排序,是否保证在执行std::unique 后保持排序?

或者还有其他(可能更有效)的方法来完成这一切吗?

【问题讨论】:

  • 我假设您没有选择在插入之前进行检查以避免首先出现欺骗?
  • 正确。那将是理想的。
  • 我建议更正上面的代码,或者真的指出它是错误的。 std::unique 假定范围已经排序。
  • 使用集合代替

标签: c++ sorting vector stl duplicates


【解决方案1】:

std::unique 仅适用于重复元素的连续运行,因此您最好先排序。但是,它是稳定的,因此您的向量将保持排序状态。

【讨论】:

    【解决方案2】:

    您需要在调用unique 之前对其进行排序,因为unique 只会删除彼此相邻的重复项。

    编辑:38 秒...

    【讨论】:

      【解决方案3】:

      unique 只删除连续的重复元素(这是线性时间运行所必需的),因此您应该先执行排序。调用unique 后,它将保持排序。

      【讨论】:

        【解决方案4】:

        std::unique 仅删除相邻的重复元素:您必须先对向量进行排序,然后它才能按预期工作。

        std::unique 被定义为稳定的,所以向量在其上运行 unique 后仍然会排序。

        【讨论】:

          【解决方案5】:

          如前所述,unique 需要一个排序容器。此外,unique 实际上并没有从容器中删除元素。相反,它们被复制到最后,unique 返回一个指向第一个这样的重复元素的迭代器,您应该调用 erase 来实际删除这些元素。

          【讨论】:

          • unique 是否需要一个已排序的容器,还是只是简单地重新排列输入序列以使其不包含相邻的重复项?我认为是后者。
          • @Pate,你是对的。它不需要一个。它会删除相邻的重复项。
          • 如果您有一个可能有重复的容器,并且您想要一个容器中没有任何重复值的容器,那么您必须首先对容器进行排序,然后将其传递给唯一的,并且然后使用擦除实际删除重复项。如果您只是想删除相邻的重复项,则不必对容器进行排序。但是您最终会得到重复的值: 1 2 2 3 2 4 2 5 2 将更改为 1 2 3 2 4 2 5 2 如果传递给 unique 而不排序, 1 2 3 4 5 如果排序,传递给 unique 并擦除.
          【解决方案6】:

          我不确定您使用它的目的是什么,所以我不能 100% 肯定地说,但通常当我想到“排序的、唯一的”容器时,我会想到 std::set。它可能更适合您的用例:

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

          否则,在调用唯一之前进行排序(正如其他答案指出的那样)是要走的路。

          【讨论】:

          • 言归正传! std::set 被指定为排序的唯一集。大多数实现使用高效的有序二叉树或类似的东西。
          • +1 也想到了设置。不想重复这个答案
          • std::set 是否保证被排序?在实践中它是有道理的,但标准是否要求它?
          • 是的,见 23.1.4.9 “关联容器迭代器的基本属性是它们以键的非降序遍历容器,其中非降序由所使用的比较定义建造它们”
          • @MadCoder:集合以排序的方式实现并不一定“有意义”。还有一些使用哈希表实现的集合,它们没有排序。事实上,大多数人更喜欢在可用时使用哈希表。但是 C++ 中的命名约定恰好是排序的关联容器被简单地命名为“set”/“map”(类似于 Java 中的 TreeSet/TreeMap);并且被排除在标准之外的散列关联容器称为“hash_set”/“hash_map”(SGI STL)或“unordered_set”/“unordered_map”(TR1)(类似于Java中的HashSet和HashMap)
          【解决方案7】:

          效率是一个复杂的概念。有时间与空间的考虑,以及一般测量(您只能得到模糊的答案,例如 O(n))与特定的测量(例如,冒泡排序可能比快速排序快得多,具体取决于输入特征)。

          如果您的重复项相对较少,那么排序后跟唯一并擦除似乎是要走的路。如果您有相对较多的重复项,则从向量创建一个集合并让它完成繁重的工作可以轻松击败它。

          也不要只关注时间效率。排序+唯一+擦除在 O(1) 空间中运行,而集合构造在 O(n) 空间中运行。并且两者都不直接适用于 map-reduce 并行化(对于真正的 huge 数据集)。

          【讨论】:

          • 什么会给你 map/reduce 能力?我能想到的唯一一种是分布式合并排序,你仍然可以在最终合并中只使用一个线程。
          • 是的,您必须有一个控制节点/线程。但是,您可以根据需要多次划分问题,以对控制/父线程处理的工作线程/子线程的数量以及每个叶节点必须处理的数据集的大小设置上限。并非所有问题都可以通过 map-reduce 轻松解决,我只是想指出有些人处理类似(表面上,无论如何)优化问题,其中处理 10 TB 的数据被称为“星期二”。
          【解决方案8】:

          我同意R. PateTodd Gardnerstd::set 在这里可能是个好主意。即使您无法使用向量,但如果您有足够多的重复项,您最好创建一个集合来完成这项繁琐的工作。

          让我们比较三种方法:

          只使用向量,排序+唯一

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

          转换为设置(手动)

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

          转换为集合(使用构造函数)

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

          随着重复数量的变化,这些表现如何:

          总结:当重复的数量足够大时,实际上转换为集合然后将数据转储回向量会更快

          由于某种原因,手动进行集合转换似乎比使用集合构造函数更快——至少在我使用的玩具随机数据上。

          【讨论】:

          • 令我震惊的是,构造方法始终比手动方法差很多。你会认为除了一些微小的持续开销之外,它只会做手动的事情。谁能解释一下?
          • 酷,感谢图表。您能否了解重复次数的单位是什么? (即,“足够大”大约有多大)?
          • @Kyle:它很大。我为这张图使用了 1,000,000 个随机抽取的介于 1 和 1000、100 和 10 之间的整数的数据集。
          • 我认为你的结果是错误的。在我的测试中,重复元素越多,向量(比较)越快,实际上相反。您是否在优化和运行时检查关闭的情况下进行编译?在我这边,向量总是更快,取决于重复的数量,最高可达 100 倍。 VS2013,cl /Ox -D_SECURE_SCL=0。
          • 似乎缺少 x 轴的描述。
          【解决方案9】:

          这里有一个模板可以帮你做:

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

          这样称呼它:

          removeDuplicates<int>(vectorname);
          

          【讨论】:

          • +1 模板化! - 但你可以只写 removeDuplicates(vec),而不显式指定模板参数
          • 或者更好的是,直接使用模板化迭代器(开始和结束),您可以在除向量之外的其他结构上运行它。
          • 见鬼,模板!快速修复小列表,完整的 STL 风格。 +1 thx
          • @Kyle - 仅在具有erase() 方法的其他容器上,否则您必须返回新的结束迭代器并让调用代码截断容器。
          【解决方案10】:

          Nate Kohl 建议的标准方法,只使用向量、排序 + 唯一:

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

          不适用于指针向量。

          仔细看this example on cplusplus.com

          在他们的示例中,移到末尾的“所谓的重复项”实际上显示为? (未定义的值),因为那些“所谓的重复项”有时是“额外元素”,有时在原始向量中存在“缺失元素”。

          在指向对象的指针向量上使用std::unique() 时会出现问题(内存泄漏、从 HEAP 读取数据错误、重复释放,这会导致分段错误等)。

          这是我对问题的解决方案:将 std::unique() 替换为 ptgi::unique()

          请参阅下面的文件 ptgi_unique.hpp:

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

          这是我用来测试它的 UNIT 测试程序:

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

          【讨论】:

          • 我不明白这里的理由。因此,如果您有一个指针容器,并且想要删除重复项,那么这将如何影响指针指向的对象?不会发生内存泄漏,因为至少有一个指针(并且在这个容器中正好有一个)指向它们。好吧,好吧,我猜你的方法可能有一些奇怪的重载运算符或需要特别考虑的奇怪比较函数的优点。
          • 不确定我是否理解你的意思。以 vector 为例,其中 4 个指针指向整数 {1, 2. 2, 3}。它已排序,但在调用 std::unique 之后,这 4 个指针是指向整数 {1、2、3、3} 的指针。现在你有两个相同的指向 3 的指针,所以如果你调用 delete,它会重复删除。坏的!其次,请注意第二个 2 丢失了,这是内存泄漏。
          • kccqzy,下面是示例程序,让您更好地理解我的答案:
          • @joe:即使在std::unique 之后你有 [1, 2, 3, 2] 你也不能在 2 上调用 delete ,因为那样会留下一个指向 2 的悬空指针! => 不要对newEnd = std::uniquestd::end 之间的元素调用delete,因为[std::begin, newEnd) 中仍有指向这些元素的指针!
          • @ArneVogel:也许对于“工作正常”的琐碎值。在vector&lt;unique_ptr&lt;T&gt;&gt; 上调用unique 相当没有意义,因为这样的向量可以包含的唯一重复值是nullptr
          【解决方案11】:

          这是 std::unique() 出现的重复删除问题的示例。在 LINUX 机器上,程序崩溃。阅读 cmets 了解详情。

          // 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:我也跑了“valgrind ./Main10”,valgrind没有发现问题。我强烈建议所有使用 LINUX 的 C++ 程序员使用这个非常高效的工具,尤其是如果您正在编写必须 24x7 运行且永不泄漏或崩溃的实时应用程序!
          • std::unique 问题的核心可以总结为“std::unique 在未指定状态下返回重复项”!!!!!!!为什么标准委员会这样做,我永远不会知道。委员会成员.. 任何 cmets ???
          • 是的,“std::unique 返回未指定状态的重复项”。所以,根本不要依赖一个已经“唯一”的数组来手动管理内存!最简单的方法是使用 std::unique_ptr 而不是原始指针。
          • 这似乎是对不同答案的回应;它没有回答问题(其中vector 包含整数,而不是指针,并且没有指定比较器)。
          【解决方案12】:
          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 个内存分配。也许更喜欢 std::move 而不是 std::copy 将整数移动到向量中而不是复制它们,因为以后不需要该集合。
          【解决方案13】:

          我重做了 Nate Kohl 的分析并得到了不同的结果。对于我的测试用例,直接对向量进行排序总是比使用集合更有效。我添加了一种更有效的新方法,使用unordered_set

          请记住,unordered_set 方法只有在您对需要唯一和排序的类型具有良好的哈希函数时才有效。对于整数,这很容易! (标准库提供了一个默认的哈希,它只是身份函数。)另外,不要忘记在最后进行排序,因为 unordered_set 是无序的:)

          我在 setunordered_set 实现内部做了一些挖掘,发现构造函数实际上为每个元素构造了一个新节点,然后检查它的值以确定它是否应该实际插入(在 Visual Studio 实现中,在至少)。

          以下是 5 种方法:

          f1:只使用vectorsort + unique

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

          f2:转换为set(使用构造函数)

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

          f3:转换为set(手动)

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

          f4:转换为unordered_set(使用构造函数)

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

          f5:转换为unordered_set(手动)

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

          我使用在 [1,10]、[1,1000] 和 [1,100000] 范围内随机选择的 100,000,000 个整数的向量进行了测试

          结果(以秒为单位,越小越好):

          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
          

          【讨论】:

          • 对于整数,可以使用基数排序,比std::sort快很多。
          • 快速提示,要使用sortunique 方法,你必须#include &lt;algorithm&gt;
          • @ChangmingSun 我想知道为什么优化器似乎在 f4 上失败了?这些数字与 f5 有很大不同。这对我来说没有任何意义。
          • @sandthorn 正如我在回答中所解释的那样,该实现为输入序列中的每个元素构建一个节点(包括动态分配),这对于最终成为重复的每个值都是浪费的。优化器不可能知道它可以跳过它。
          • 再次有趣的是,使用手动转换 f5 比使用构造函数 f4 运行得快得多!
          【解决方案14】:

          关于 alexK7 基准测试。我尝试了它们并得到了相似的结果,但是当值的范围是 100 万时,使用 std::sort (f1) 和使用 std::unordered_set (f5) 的情况产生相似的时间。当取值范围为 1000 万时,f1 比 f5 快。

          如果值的范围是有限的并且值是无符号整数,则可以使用 std::vector,其大小对应于给定的范围。代码如下:

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

          【讨论】:

            【解决方案15】:

            如果你不想改变元素的顺序,那么你可以试试这个解决方案:

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

            【讨论】:

            • 也许使用 unordered_set 而不是 set(如果可用,则使用 boost::remove_erase_if)
            【解决方案16】:

            你可以这样做:

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

            【讨论】:

              【解决方案17】:

              如果您正在寻找性能并使用std::vector,我推荐documentation link 提供的那个。

              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 绝不是官方文档。
              【解决方案18】:
              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;}}}}
              

              这是我创建的一个函数,可用于删除重复。需要的头文件只有&lt;iostream&gt;&lt;vector&gt;

              【讨论】:

                【解决方案19】:

                更易理解的代码来自: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";
                }
                

                输出:

                1 2 3 4 5 6 7
                

                【讨论】:

                  【解决方案20】:

                  假设 a 是一个向量,使用

                  删除连续的重复项

                  a.erase(unique(a.begin(),a.end()),a.end());O(n) 时间内运行。

                  【讨论】:

                  • 连续重复。好的,所以它首先需要一个std::sort
                  【解决方案21】:

                  有了 Ranges v3 库,您可以简单地使用

                  action::unique(vec);
                  

                  请注意,它实际上删除了重复的元素,而不仅仅是移动它们。

                  不幸的是,C++20 中的操作并未标准化,因为范围库的其他部分即使在 C++20 中您仍然必须使用原始库。

                  【讨论】:

                  • 不幸的是,C++20 中没有actions
                  【解决方案22】:
                  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;
                  }
                  

                  【讨论】:

                  • 欢迎来到 StackOverflow!请edit您的问题添加对如何您的代码工作的解释,以及为什么它与其他答案相同或更好。这个问题已有十多年的历史,并且已经有许多很好的、解释清楚的答案。如果没有对您的解释,它就没有那么有用,并且很有可能被否决或删除。
                  【解决方案23】:

                  如果你的类很容易转换为 int,并且你有一些内存, unique 可以在没有排序之前完成,而且速度要快得多:

                  #include <vector>
                  #include <stdlib.h>
                  #include <algorithm>
                  int main (int argc, char* argv []) {
                    //vector init
                    std::vector<int> v (1000000, 0);
                    std::for_each (v.begin (), v.end (), [] (int& s) {s = rand () %1000;});
                    std::vector<int> v1 (v);
                    int beg (0), end (0), duration (0);
                    beg = clock ();
                    {
                      std::sort (v.begin (), v.end ());
                      auto i (v.begin ());
                      i = std::unique (v.begin (), v.end ());
                      if (i != v.end ()) v.erase (i, v.end ());
                    }
                    end = clock ();
                    duration = (int) (end - beg);
                    std::cout << "\tduration sort + unique == " << duration << std::endl;
                  
                    int n (0);
                    duration = 0;
                    beg = clock ();
                    std::for_each (v1.begin (), v1.end (), [&n] (const int& s) {if (s >= n) n = s+1;});
                    std::vector<int> tab (n, 0);
                    {
                      auto i (v1.begin ());
                      std::for_each (v1.begin (), v1.end (), [&i, &tab] (const int& s) {
                        if (!tab [s]) {
                          *i++ = s;
                          ++tab [s];
                        }
                      });
                      std::sort (v1.begin (), i);
                      v1.erase (i, v1.end ());
                    }
                    end = clock ();
                    duration = (int) (end - beg);
                    std::cout << "\tduration unique + sort == " << duration << std::endl;
                    if (v == v1) {
                      std::cout << "and results are same" << std::endl;
                    }
                    else {
                      std::cout << "but result differs" << std::endl;
                    }  
                  }
                  

                  典型结果: 持续时间排序 + 唯一 == 38985 持续时间唯一 + 排序 == 2500 结果是一样的

                  【讨论】:

                    【解决方案24】:

                    大多数答案似乎是使用O(nlogn),但使用unordered_set,我们可以将其减少到O(n)。我看到了一些使用sets 的解决方案,但我发现了这个,使用setiterators 似乎更优雅。

                    using Intvec = std::vector<int>;
                    
                    void remove(Intvec &v) {
                        // creating iterator starting with beginning of the vector 
                        Intvec::iterator itr = v.begin();
                        std::unordered_set<int> s;
                        // loops from the beginning to the end of the list 
                        for (auto curr = v.begin(); curr != v.end(); ++curr) {
                            if (s.insert(*curr).second) { // if the 0 curr already exist in the set
                                *itr++ = *curr; // adding a position to the iterator 
                            }
                        }
                        // erasing repeating positions in the set 
                        v.erase(itr, v.end());
                    }
                    

                    【讨论】:

                      猜你喜欢
                      • 2010-11-07
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多