Який найефективніший спосіб стерти дублікати та сортувати вектор?


274

Мені потрібно взяти вектор C ++ з потенційно великою кількістю елементів, стерти дублікати та сортувати його.

На даний момент у мене є код нижче, але він не працює.

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

Як я можу правильно це зробити?

Крім того, чи швидше спочатку стерти дублікати (подібно до закодованого вище) або виконати сортування спочатку? Якщо я виконую сортування першим, чи гарантовано він залишиться відсортований післяstd::unique буде виконано?

Або є інший (можливо, більш ефективний) спосіб зробити все це?


3
Я припускаю, що ви не маєте можливості перевірити перед вставкою, щоб у першу чергу не мати дупнів?
Джо

Правильно. Це було б ідеально.
Кайл Райан

29
Я б запропонував виправити код вище, або дійсно зазначити, що це WRONG. std :: унікальний передбачає, що діапазон вже відсортований.
Матьє М.

Відповіді:


584

Я згоден з Р. Пейтом і Тоддом Гарднером ; аstd::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() );

Ось як вони виконують зміни кількості дублікатів:

порівняння векторного та заданого підходів

Підсумок : коли кількість дублікатів досить велика, насправді швидше перетворити на набір, а потім скинути дані назад у вектор .

І чомусь перетворення набору вручну здається швидше, ніж використання конструктора наборів - принаймні, на іграшкових випадкових даних, які я використовував.


61
Я вражений тим, що конструкторський підхід послідовно вимірюється гірше, ніж ручний. Ви б хотіли, щоб крім деяких крихітних постійних накладних витрат, це було б просто ручною справою. Хтось може це пояснити?
Арі

17
Класно, дякую за графік. Чи можете ви дати зрозуміти, що таке одиниці для кількості копій? (тобто, навколо, наскільки "достатньо великий")?
Кайл Райан

5
@Kyle: Він досить великий. Для цього графа я використав набори 1 000 000 випадково намальованих цілих чисел від 1 до 1000, 100 та 10.
Нейт Кол

5
Я думаю, що ваші результати неправильні. У моїх тестах, чим більше дублюються елементів, тим швидший вектор (порівняльний), насправді масштабується навпаки. Ви компілювали оптимізацію щодо ввімкнення та перевірки часу виконання? На моєму боці вектор завжди швидший, до 100 разів залежно від кількості дублікатів. VS2013, cl / Ox -D_SECURE_SCL = 0.
davidnr

39
Опис осі x, здається, відсутнє.
BartoszKP

72

Я переробив профайли Нейт Коля і отримав різні результати. Для мого тестового випадку безпосередньо сортування вектора завжди ефективніше, ніж використання набору. Я додав новий більш ефективний метод, використовуючиunordered_set .

Майте на увазі, що unordered_setметод працює лише в тому випадку, якщо у вас є хороша хеш-функція для типу, який вам потрібен унікальний і відсортований. Для ints це легко! (Стандартна бібліотека пропонує хеш за замовчуванням, який є просто функцією ідентичності.) Також не забудьте сортувати в кінці, оскільки unororder_set є, ну, не упорядкований :)

Я здійснив деякий копання всередині setта unordered_setвпровадження та виявив, що конструктор фактично побудує новий вузол для кожного елемента, перш ніж перевірити його значення, щоб визначити, чи потрібно його насправді вставляти (принаймні в реалізації Visual Studio).

Ось 5 методів:

f1: Просто використовуючи vector, sort+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() );

Я зробив тест з вектором 100 000 000 точок, вибраним випадковим чином у діапазонах [1,10], [1,1000] та [1,100000]

Результати (за лічені секунди краще менше):

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
Для цілих чисел можна використовувати сортування radix, яке набагато швидше, ніж std :: sort.
Changming Sun

2
Швидкий підказки, використовувати sortабо uniqueметоди, ви повинні#include <algorithm>
Davmrtl

3
@ChangmingSun Цікаво, чому оптимізатор, здавалося, не працює на f4? Цифри різко відрізняються від f5. Це не має для мене сенсу.
пісочниця

1
@sandthorn Як пояснено у моїй відповіді, реалізація побудує вузол (включаючи динамічне розподілення) для кожного елемента з послідовності введення, який марно витрачається на кожне значення, що в кінцевому підсумку є дублікатом. Оптимізатор не може знати, що це може пропустити.
alexk7

Ах, це нагадує мені одну з розмов Скотта Мейєра про CWUK сценеріо, яке має характер розповсюдження, щоб уповільнити emplaceвид будівництва.
пісочниця

57

std::unique видаляє дублюючі елементи лише у тому випадку, якщо вони сусіди: вам слід спочатку сортувати вектор, перш ніж він буде працювати так, як ви плануєте.

std::unique визначається як стабільний, тому вектор все ще буде відсортований після запуску унікального на ньому.


42

Я не впевнений, для чого ви це використовуєте, тому я не можу це сказати зі 100% впевненістю, але зазвичай, коли я думаю, що "відсортований, унікальний" контейнер, я думаю про std :: set . Це може бути краще підходить для вашої користувальної скриньки:

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

В іншому випадку сортування до виклику унікального (як вказували інші відповіді) - це шлях.


Ну до речі! std :: set задано для сортування унікального набору. Більшість реалізацій використовують ефективне упорядковане бінарне дерево або щось подібне.
notnoop

+1 Думка про комплект. Не хотіли дублювати цю відповідь
Том,

Чи гарантовано сортування std :: set? Має сенс, що на практиці це було б, але чи цього вимагає стандарт?
MadCoder

1
Так, див. 23.1.4.9 "Основна властивість ітераторів асоціативних контейнерів полягає в тому, що вони перебирають контейнери в порядку спадання клавіш, де неспадання визначається порівнянням, яке було використано для їх побудови"
Тод Гарднер

1
@MadCoder: Це не обов'язково "має сенс", що набір реалізований таким чином, який сортується. Існують також набори, реалізовані за допомогою хеш-таблиць, які не сортуються. Насправді більшість людей вважають за краще використовувати хеш-таблиці, коли вони доступні. Але конвенція іменування в C ++ так буває, що відсортовані асоціативні контейнери називаються просто "set" / "map" (аналогічно TreeSet / TreeMap в Java); а хешовані асоціативні контейнери, які залишились поза стандартом, називаються "hash_set" / "hash_map" (SGI STL) або "unordered_set" / "unordered_map" (TR1) (аналогічно HashSet і HashMap в Java)
newacct

22

std::uniqueпрацює лише на послідовних запусках повторюваних елементів, тож краще спочатку відсортувати. Однак він стабільний, тому ваш вектор залишатиметься сортованим.


18

Ось шаблон, як це зробити для вас:

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

2
+1 Шаблонуйте геть! - але ви можете просто написати deleteDuplicates (vec), не чітко вказуючи аргументи шаблону
Faisal Vali

10
Або ще краще, просто потрібно взяти шаблонні ітератори безпосередньо (почати і закінчити), і ви можете запустити його на інших структурах, крім вектора.
Кайл Райан

Пекло так, шаблони! швидке виправлення невеликих списків, повний стиль STL. +1 thx
QuantumKarl

@Kyle - лише для інших контейнерів, у яких є erase()метод, в іншому випадку ви повинні повернути новий кінцевий ітератор і мати викликовий код урізати контейнер.
Toby Speight

8

Ефективність - це складна концепція. Існують міркування щодо часу та простору, а також загальні вимірювання (де ви отримуєте лише розпливчасті відповіді, такі як O (n)) та конкретні (наприклад, сортування міхура може бути набагато швидшим, ніж швидкодіючий, залежно від вхідних характеристик).

Якщо у вас є відносно мало дублікатів, то сортування з наступним унікальним та стиранням здається шляхом. Якщо у вас було відносно багато дублікатів, створення набору з вектора і дозволяючи йому робити важкий підйом, ви могли легко його обіграти.

Не концентруйтеся лише на ефективності часу. Сортування + унікальне + стирання працює в просторі O (1), тоді як набір конструкцій працює в просторі O (n). І жоден з них прямо не піддається паралелізації паралельних карт (для дійсно величезних наборів даних).


Що дало б вам можливість зменшення / зменшення? Єдине, про що я можу придумати, - це розподілений сорт злиття, і ви все одно можете використовувати лише один потік у остаточному злитті.
Зан Лінкс,

1
Так, у вас повинен бути один керуючий вузол / потік. Однак ви можете розділити проблему стільки разів, скільки потрібно, щоб розмістити верхні межі кількості робочих / дочірніх потоків, якими займається контрольний / батьківський потік, та розміру набору даних, який повинен обробляти кожен вузол листів. Не всі проблеми легко вирішити із зменшенням карти, я просто хотів зазначити, що є люди, які мають справу з подібними (на поверхні, все одно) проблемами оптимізації, де обробка 10-ти терабайтних даних називається "вівторок".

7

Ви повинні сортувати його перед тим, як дзвонити, uniqueоскільки uniqueвидаляє лише дублікати, які знаходяться поруч.

редагування: 38 секунд ...


7

uniqueвидаляє лише послідовні повторювані елементи (що необхідно для запуску в лінійний час), тому вам слід виконати сортування спочатку. Він буде відсортований після виклику до unique.


7

Якщо ви не хочете змінювати порядок елементів, тоді ви можете спробувати це рішення:

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

Можливо, використовуйте unororder_set замість set (і boost :: remove_erase_if якщо є)
gast128

4

Припускаючи, що a - вектор, видаліть суміжні дублікати за допомогою

a.erase(unique(a.begin(),a.end()),a.end());працює в O (n) час.


1
суміжні дублікати. добре, значить, це потрібно std::sortспочатку.
v.oddou

2

Як уже було сказано, uniqueпотрібен відсортований контейнер. Крім того, uniqueфактично не вилучає елементи з контейнера. Натомість вони копіюються до кінця, uniqueповертає ітератор, що вказує на перший такий дублікат, і ви, як очікується, зателефонуєте eraseфактично видалити елементи.


Чи потрібен унікальний відсортований контейнер, чи він просто лише переставляє послідовність введення, щоб він не містив суміжних дублікатів? Я подумав останнє.

@Pate, ви маєте рацію. Цього не потрібно. Він видаляє сусідні дублікати.
Білл Лінч

Якщо у вас є контейнер, у якого можуть бути дублікати, і ви хочете, щоб контейнер не мав дублюваних значень ніде в контейнері, то спочатку потрібно сортувати контейнер, потім передати його до унікального, а потім використати стирання, щоб фактично видалити дублікати . Якщо ви просто хочете видалити сусідні дублікати, вам не доведеться сортувати контейнер. Але ви отримаєте скопійовані значення: 1 2 2 3 2 4 2 5 2 буде змінено на 1 2 3 2 4 2 5 2, якщо буде передано унікальне без сортування, 1 2 3 4 5 якщо відсортовано, передано до унікального та стерти .
Макс Лібберт

2

Стандартний підхід, запропонований Нейт Колем, просто використовуючи вектор, sort + унікальний:

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

не працює для вектору покажчиків.

Подивіться уважно на цей приклад на cplusplus.com .

У їх прикладі "так звані дублікати", переміщені до кінця, насправді відображаються як? (невизначені значення), оскільки ці "так звані дублікати" є SOMETIMES "зайвими елементами", а SOMETIMES є "відсутні елементи", які були у вихідному векторі.

Проблема виникає при використанні 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";
}

Я не розумію обґрунтування тут. Отже, якщо у вас є контейнер з покажчиками і ви хочете видалити дублікати, як це впливає на об'єкти, на які вказують вказівники? Жодних витоків пам'яті не відбудеться, тому що в цьому контейнері є принаймні один вказівник (і саме той, який вказує на них). Ну, я думаю, ваш метод може мати певну користь з якимись дивними перевантаженими операторами або дивними функціями порівняння, які вимагають особливої ​​уваги.
kccqzy

Не впевнений, чи я розумію вашу думку. Візьмемо простий випадок вектора <int *>, де 4 вказівника вказують на цілі числа {1, 2. 2, 3}. Її відсортовано, але після виклику std :: unique, 4 вказівника є вказівниками на цілі числа {1, 2, 3, 3}. Тепер у вас є два однакових вказівника на 3, тож якщо ви зателефонуєте на видалення, це буде повторно видалено. БАД! По-друге, зауважте, що 2-й 2 промахується, витік пам'яті.
Джо

kccqzy, ось приклад програми, щоб ви краще зрозуміли мою відповідь:
Joe

@joe: Навіть якщо після std::uniqueтого, як у вас [1, 2, 3, 2] ви не можете зателефонувати видалити на 2, це залишило б звисаючий покажчик на 2! => Просто не викликайте видалення на елементах між newEnd = std::uniqueі, std::endоскільки у вас ще є вказівники на ці елементи [std::begin, newEnd)!
MFH

2
@ArneVogel: Можливо, для тривіальних значень "добре працює". Дуже безглуздо закликати uniqueдо а vector<unique_ptr<T>>, оскільки єдине дублюване значення такого вектора може містити nullptr.
Бен Войгт

2

З бібліотекою діапазонів (що надходить у C ++ 20) ви можете просто користуватися

action::unique(vec);

Зауважте, що він фактично видаляє дублюючі елементи, а не просто переміщувати їх.


1

Про тести alexK7. Я спробував їх і отримав подібні результати, але коли діапазон значень становить 1 мільйон випадків, використовуючи std :: sort (f1) та використовуючи std :: unordered_set (f5), створюється аналогічний час. Коли діапазон значень становить 10 мільйонів f1, це швидше, ніж f5.

Якщо діапазон значень обмежений і значення не підписані int, можна використовувати 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;
    }
}


1

Якщо ви шукаєте продуктивність та використовуєте std::vector, я рекомендую ту, яку надає ця посилання на документацію .

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 жодним чином не є офіційною документацією.
Ілля Попов

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
можливо змінити розмір вектора після його очищення, щоб при побудові вектора було виділено лише 1 розподіл пам'яті. Можливо, віддайте перевагу std :: move замість std :: copy, щоб перемістити вставки у вектор, а не копіювати їх, оскільки набір пізніше не знадобиться.
YoungJohn

0

Якщо ви не хочете змінювати вектор (стирати, сортувати), ви можете використовувати бібліотеку Ньютона. У підбібліотеці алгоритму є виклик функції, copy_single

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

тож Ви можете:

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

де копія - вектор, куди ви хочете push_back копію унікальних елементів. але пам’ятайте, що ви push_back елементи, і ви не створюєте новий вектор

все одно, це швидше, тому що ви не стираєте () елементи (що займає багато часу, за винятком випадків, коли ви pop_back () через перепризначення)

Я роблю кілька експериментів, і це швидше.

Також ви можете використовувати:

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

іноді все-таки швидше.


1
Ця функція присутня в стандартній бібліотеці як unique_copy.
LF

0

Більш зрозумілий код від: 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

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! Будь ласка редагувати своє питання , щоб додати пояснення , як вам код працює, і чому це еквівалентно або краще , ніж інші відповіді. На це питання більше десяти років , і вже є багато хороших, добре пояснених відповідей. Без ваших пояснень, це не так корисно і має хороші шанси бути знятим або знятим.
Das_Geek

-1

Ось приклад проблеми дублювання видалення, яка виникає з std :: unique (). На машині LINUX програма виходить з ладу. Прочитайте коментарі для деталей.

// 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 не знайшов жодних проблем. Я настійно рекомендую всім програмістам на C ++, які використовують LINUX, використовувати цей дуже продуктивний інструмент, особливо якщо ви пишете додатки в режимі реального часу, які повинні працювати 24x7, і ніколи не протікати і не виходити з ладу!
Джо

Серце проблеми з std :: унікальним можна підсумувати цим твердженням "std :: унікальний повертає дублікати у не визначеному стані" !!!!! Чому комітет з питань стандартів це зробив, я ніколи не дізнаюся. Звернути членів .. будь-які коментарі ???
Джо

1
Так, "std :: унікальний повертає дублікати у не визначеному стані". Отже, просто не покладайтеся на масив, який "унікальний" для керування пам'яттю вручну! Найпростіший спосіб зробити це - використовувати std :: unique_ptr замість необроблених покажчиків.
alexk7

Схоже, це відповідь на іншу відповідь; він не відповідає на запитання (в якому vectorмістить цілі числа, а не покажчики та не вказує компаратор).
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;}}}}

Це створена мною функція, яку ви можете використовувати для видалення повторів. Необхідні файли заголовків - це просто <iostream>та <vector>.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.