복제본을 지우고 벡터를 정렬하는 가장 효율적인 방법은 무엇입니까?


274

잠재적으로 많은 요소가있는 C ++ 벡터를 가져 와서 중복을 지우고 정렬해야합니다.

현재 아래 코드가 있지만 작동하지 않습니다.

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

이 작업을 올바르게 수행하려면 어떻게해야합니까?

또한 중복을 먼저 지우거나 (위의 코드와 유사) 정렬을 먼저 수행하는 것이 더 빠릅니까? 정렬을 먼저 수행하면 std::unique실행 된 후에도 정렬 상태 가 유지 됩니까?

아니면이 모든 것을 수행하는 또 다른 (아마도 더 효율적인) 방법이 있습니까?


3
처음에 속박을 피하기 위해 삽입 전에 검사 옵션이 없다고 가정합니까?
Joe

옳은. 이상적입니다.
Kyle Ryan

29
위의 코드를 수정하거나 실제로 잘못되었음을 나타냅니다. std :: unique는 범위가 이미 정렬되어 있다고 가정합니다.
Matthieu M.

답변:


584

나는 R. PateTodd Gardner에 동의합니다 . 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
생성자 접근법이 지속적으로 수동보다 나쁘다는 것에 놀랐습니다. 약간의 일정한 오버 헤드를 제외하고는 수동으로 할 것입니다. 누구든지 이것을 설명 할 수 있습니까?
Ari

17
멋지다, 그래프 고마워. Number of Duplicates의 단위가 무엇인지 이해할 수 있습니까? (즉, "큰 크기"정도의 크기)?
Kyle Ryan

5
@Kyle : 꽤 큽니다. 이 그래프에 1에서 1000, 100, 10 사이의 1,000,000 개의 무작위로 그려진 정수의 데이터 세트를 사용했습니다.
Nate Kohl 2016 년

5
결과가 잘못되었다고 생각합니다. 내 테스트에서 더 빠른 벡터 (비교)가 더 중복 된 요소는 실제로 다른 방법으로 확장됩니다. 최적화를 설정하고 런타임 검사를 해제하여 컴파일 했습니까?. 내 측면의 벡터는 항상 중복 수에 따라 최대 100 배 빠릅니다. VS2013, cl / Ox -D_SECURE_SCL = 0
davidnr

39
x 축 설명이 누락 된 것 같습니다.
BartoszKP

72

Nate Kohl의 프로파일을 수정하고 다른 결과를 얻었습니다. 필자의 테스트 사례에서는 벡터를 직접 정렬하는 것이 항상 집합을 사용하는 것보다 효율적입니다. 을 사용하여 더 효율적인 새로운 방법을 추가했습니다 unordered_set.

unordered_set메소드는 고유하고 정렬해야하는 유형에 적합한 해시 함수가있는 경우에만 작동합니다. ints의 경우 이것은 쉽습니다! (표준 라이브러리는 단순히 식별 함수 인 기본 해시를 제공합니다.) 또한 unordered_set이 un order_set이기 때문에 마지막에 정렬하는 것을 잊지 마십시오. :

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

[1,10], [1,1000] 및 [1,100000] 범위에서 무작위로 선택된 100,000,000 int의 벡터로 테스트를 수행했습니다.

결과 (초 단위로 작을수록 좋음) :

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
정수의 경우 기수 정렬을 사용할 수 있으며 std :: sort보다 훨씬 빠릅니다.
Changming Sun

2
빠른 팁, 사용 sort또는 unique방법#include <algorithm>
Davmrtl

3
@ChangmingSun 옵티마이 저가 왜 F4에서 실패한 것 같습니까? 숫자는 f5와 크게 다릅니다. 나에게는 이해가되지 않습니다.
sandthorn

1
@ sandthorn 내 대답에서 설명했듯이 구현은 입력 시퀀스에서 각 요소에 대해 노드 (동적 할당 포함)를 작성합니다.이 값은 중복되어 모든 값에 낭비됩니다. 옵티마이 저가이를 건너 뛸 수 있음을 알 수있는 방법은 없습니다.
alexk7

아,이 약 스콧 마이어의 회담 중 하나를 생각 나게 CWUK 시나리오에는 아래로 천천히 propablities의 성격이 emplace공사의 종류.
sandthorn

57

std::unique 이웃 인 경우 중복 요소 만 제거합니다. 원하는대로 작동하려면 먼저 벡터를 정렬해야합니다.

std::unique 는 안정적인 것으로 정의되어 있으므로 벡터를 고유하게 실행 한 후에도 벡터가 정렬됩니다.


42

나는 당신이 이것을 무엇을 사용하고 있는지 확실하지 않으므로 100 % 확실성으로 이것을 말할 수는 없지만 일반적으로 "정렬되고 독창적 인"컨테이너를 생각할 때 std :: set 생각 합니다 . 사용 사례에 더 적합 할 수 있습니다.

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

그렇지 않으면, 다른 답변이 지적한 것처럼 unique를 호출하기 전에 정렬하는 것이 좋습니다.


글쎄요! std :: set은 정렬 된 고유 세트로 지정됩니다. 대부분의 구현은 효율적인 순서의 이진 트리 또는 이와 유사한 것을 사용합니다.
notnoop

+1 세트의 생각. 이 답변을 복제하고 싶지 않았습니다
Tom

std :: set이 정렬되도록 보장됩니까? 실제로는 그렇 겠지만 표준에 따라 필요합니까?
MadCoder 2016 년

1
예. 23.1.4.9 "연관 컨테이너 반복자의 기본 속성은 컨테이너를 구성하는 데 사용 된 비교에 의해 내림차순이 정의되지 않은 키의 내림차순으로 컨테이너를 반복한다는 것입니다.
Todd Gardner

1
@MadCoder : 집합이 정렬 된 방식으로 구현되었다고 반드시 "인식"하는 것은 아닙니다. 정렬되지 않은 해시 테이블을 사용하여 구현 된 세트도 있습니다. 실제로 대부분의 사람들은 가능한 경우 해시 테이블을 사용하는 것을 선호합니다. 그러나 C ++의 명명 규칙은 정렬 된 연관 컨테이너의 이름이 단순히 "set"/ "map"(Java의 TreeSet / TreeMap과 유사 함)으로 지정됩니다. 표준에서 제외 된 해시 된 연관 컨테이너를 "hash_set"/ "hash_map"(SGI STL) 또는 "unordered_set"/ "unordered_map"(TR1) (Java의 HashSet 및 HashMap과 유사)이라고합니다.
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 유혹합니다! -그러나 템플릿 인수를 명시 적으로 지정하지 않고 removeDuplicates (vec)를 작성할 수 있습니다.
Faisal Vali

10
또는 템플릿 템플릿 반복자를 직접 시작 (시작 및 종료)하고 벡터 이외의 다른 구조에서 실행할 수 있습니다.
Kyle Ryan

그래, 템플릿! 작은 목록, 전체 STL 스타일에 대한 빠른 수정. +1 thx
QuantumKarl

@Kyle- erase()메소드 가있는 다른 컨테이너에서만 , 그렇지 않으면 새 엔드 이터레이터를 반환하고 호출 코드가 컨테이너를 자르도록해야합니다.
Toby Speight

8

효율성은 복잡한 개념입니다. 시간과 공간에 대한 고려 사항뿐만 아니라 일반적인 측정 (O (n)과 같은 모호한 답변 만 얻을 수있는 곳) 대 특정 측정치 (예 : 입력 특성에 따라 버블 정렬이 퀵 정렬보다 훨씬 빠를 수 있음)가 있습니다.

중복이 상대적으로 적 으면 정렬과 고유 및 소거가 차례로 진행됩니다. 복제본이 비교적 많은 경우 벡터에서 세트를 만들어 무거운 리프팅을 수행하면 쉽게 이길 수 있습니다.

시간 효율성에만 집중하지 마십시오. Sort + unique + erase는 O (1) 공간에서 작동하지만 설정된 구성은 O (n) 공간에서 작동합니다. 그리고지도 축소 병렬화 (실제로 거대한 데이터 세트의 경우)에 직접 적합하지 않습니다 .


지도 / 축소 능력은 무엇입니까? 내가 생각할 수있는 유일한 것은 분산 병합 정렬이며 최종 병합에서 여전히 하나의 스레드 만 사용할 수 있습니다.
Zan Lynx

1
예, 하나의 제어 노드 / 스레드가 있어야합니다. 그러나 제어 / 상위 스레드가 처리하는 작업자 / 자식 스레드 수와 각 리프 노드가 처리해야하는 데이터 세트의 크기에 상한을두기 위해 필요한만큼 문제를 나눌 수 있습니다. 모든 문제를지도 축소로 쉽게 해결할 수있는 것은 아니며, 10 테라 바이트의 데이터를 처리하는 것을 "화요일"이라고하는 유사한 최적화 문제를 다루는 사람들이 있다는 것을 지적하고 싶었습니다.


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

아마도 set 대신
unorder_set을

4

a 가 벡터 라고 가정하면 다음을 사용하여 연속 복제본을 제거하십시오.

a.erase(unique(a.begin(),a.end()),a.end());O (n) 시간으로 실행됩니다 .


1
연속 복제. 좋아, 그래서 std::sort첫 번째가 필요합니다 .
v.oddou

2

이미 언급했듯이 unique정렬 된 컨테이너가 필요합니다. 또한 unique실제로 컨테이너에서 요소를 제거하지는 않습니다. 대신, 그것들은 끝으로 복사되고 unique첫 번째 복제 요소를 가리키는 반복자를 반환하며 erase실제로 요소를 제거 하기 위해 호출 해야합니다.


고유 한 분류 컨테이너가 필요합니까, 아니면 단순히 인접한 복제본을 포함하지 않도록 입력 순서 만 재 배열합니까? 나는 후자를 생각했다.

@ 페이트, 당신은 맞습니다. 필요하지 않습니다. 인접한 중복을 제거합니다.
Bill Lynch

중복이있을 수있는 컨테이너가 있고 컨테이너의 어느 곳에도 중복 값이없는 컨테이너를 원하면 컨테이너를 먼저 정렬 한 다음 고유하게 전달 한 다음 지우기를 사용하여 실제로 중복을 제거해야합니다 . 인접한 중복 항목을 제거하려면 컨테이너를 정렬 할 필요가 없습니다. 그러나 중복 된 값으로 끝납니다 : 1 2 2 3 2 4 2 5 2는 정렬하지 않고 고유로 전달되면 1 2 3 2 4 2 5 2로 변경됩니다. .
Max Lybbert 2018 년

2

Nate Kohl이 제안한 표준 접근법은 벡터, 정렬 + 고유를 사용합니다.

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 2013

내가 당신의 요점을 이해하는지 확실하지 않습니다. 4 개의 포인터가 정수 {1, 2, 2, 3}을 가리키는 vector <int *>의 간단한 사례를 보자. 정렬되었지만 std :: unique를 호출하면 4 개의 포인터가 정수 {1, 2, 3, 3}에 대한 포인터입니다. 이제 3에 대한 두 개의 동일한 포인터가 있으므로 delete를 호출하면 중복 삭제가 수행됩니다. 나쁜! 둘째, 2nd 2가 누락되어 메모리 누수가 발생합니다.
joe

kccqzy, 여기 내 대답을 더 잘 이해할 수있는 예제 프로그램이 있습니다.
joe

@joe : std::unique[1, 2, 3, 2] 후에도 2에 대해 댕글 링 포인터를 남기므로 2에 대해 delete를 호출 할 수 없습니다! = 간단히 사이의 요소 삭제 호출하지 않습니다> newEnd = std::uniquestd::end여전히 이러한 요소에 대한 포인터를 가지고 [std::begin, newEnd)!
MFH

2
@ArneVogel : 아마도 "잘 작동한다"라는 사소한 가치를 위해 . 이러한 벡터가 포함 할 수있는 유일한 중복 값 uniquevector<unique_ptr<T>>이므로 를 호출 하는 것은 의미 가 없습니다 nullptr.
벤 보이 그

2

Ranges 라이브러리 (C ++ 20으로 제공)를 사용하면 간단하게 사용할 수 있습니다

action::unique(vec);

실제로는 이동하는 것이 아니라 복제 된 요소를 제거합니다.


1

alexK7 벤치 마크 정보 나는 그것들을 시도하고 비슷한 결과를 얻었지만, 값의 범위가 백만일 때 std :: sort (f1)를 사용하고 std :: unordered_set (f5)를 사용하는 경우에도 비슷한 시간이 발생합니다. 값의 범위가 1000 만인 경우 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

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


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은 공식 문서가 아닙니다.
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
벡터를 지을 때 메모리 할당이 하나만 있도록 벡터를 지우고 크기를 조정하십시오. 아마도 세트가 나중에 필요하지 않으므로 std :: copy 대신 std :: move를 선호하여 int를 복사하는 대신 벡터로 이동하십시오.
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);

여기서 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에 오신 것을 환영합니다! 제발 편집 귀하의 질문에 대한 설명을 추가하는 방법을 당신에게 코드의 작품, 그리고 그것의 동등하거나 더 나은 다른 답변보다. 이 질문은 10 세 이상 되었으며 이미 잘 설명되어있는 많은 대답이 있습니다. 설명이 없으면 유용하지 않으며 다운 보트 또는 삭제 될 가능성이 높습니다.
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";
}

추신 : 나는 또한 "valgrind ./Main10"을 실행했으며 valgrind는 아무런 문제도 발견하지 못했습니다. 24x7을 실행해야하며 실시간으로 누출되거나 충돌하지 않아야하는 실시간 응용 프로그램을 작성하는 경우 LINUX를 사용하는 모든 C ++ 프로그래머가이 매우 생산적인 도구를 사용하는 것이 좋습니다.
joe

std :: unique의 문제점의 핵심은 "std :: unique가 지정되지 않은 상태에서 중복을 리턴합니다"라는 문장으로 요약 할 수 있습니다. 표준위원회가 왜 그렇게했는지 모르겠습니다. 회원을 헌신하십시오. 모든 의견 ???
joe

1
예, "std :: unique는 지정되지 않은 상태에서 중복을 반환합니다." 따라서 메모리를 수동으로 관리하기 위해 "고유 한"어레이에 의존하지 마십시오! 가장 간단한 방법은 raw 포인터 대신 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>.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.