如何从未排序的 std::vector 中删除重复项,同时使用算法保持原始排序?

问题描述 投票:0回答:9

我有一个整数数组,需要从中删除重复项,同时保持每个整数第一次出现的顺序。我可以看到这样做,但想象一下有更好的方法可以更好地利用 STL 算法吗?插入超出了我的控制范围,因此我无法在插入之前检查是否有重复项。

int unsortedRemoveDuplicates(std::vector<int> &numbers) {
    std::set<int> uniqueNumbers;
    std::vector<int>::iterator allItr = numbers.begin();
    std::vector<int>::iterator unique = allItr;
    std::vector<int>::iterator endItr = numbers.end();

    for (; allItr != endItr; ++allItr) {
        const bool isUnique = uniqueNumbers.insert(*allItr).second;

        if (isUnique) {
            *unique = *allItr;
            ++unique;
        }
    }

    const int duplicates = endItr - unique;

    numbers.erase(unique, endItr);
    return duplicates;
}

如何使用 STL 算法来完成此操作?

c++ duplicates stdvector stl-algorithm stdset
9个回答
18
投票

听起来像是 std::copy_if 的工作。定义一个谓词来跟踪已处理的元素,如果已处理则返回 false。

如果你没有C++11支持,你可以使用笨拙的命名std::remove_copy_if并反转逻辑。

这是一个未经测试的示例:

template <typename T>
struct NotDuplicate {
  bool operator()(const T& element) {
    return s_.insert(element).second; // true if s_.insert(element);
  }
 private:
  std::set<T> s_;
};

然后

std::vector<int> uniqueNumbers;
NotDuplicate<int> pred;
std::copy_if(numbers.begin(), numbers.end(), 
             std::back_inserter(uniqueNumbers),
             std::ref(pred));

其中使用

std::ref
来避免算法内部复制有状态函子的潜在问题,尽管
std::copy_if
没有对所应用函子的副作用提出任何要求。


16
投票

最简单的方法是使用 std::set,正如每个人告诉你的那样。它太过分了,而且缓存局部性很差(慢)。

聪明*的方法是适当使用
std::vector
(确保看到底部的脚注):
#include <algorithm>
#include <vector>
struct target_less
{
    template<class It>
    bool operator()(It const &a, It const &b) const { return *a < *b; }
};
struct target_equal
{
    template<class It>
    bool operator()(It const &a, It const &b) const { return *a == *b; }
};
template<class It> It uniquify(It begin, It const end)
{
    std::vector<It> v;
    v.reserve(static_cast<size_t>(std::distance(begin, end)));
    for (It i = begin; i != end; ++i)
    { v.push_back(i); }
    std::stable_sort(v.begin(), v.end(), target_less());
    v.erase(std::unique(v.begin(), v.end(), target_equal()), v.end());
    std::sort(v.begin(), v.end());
    size_t j = 0;
    for (It i = begin; i != end && j != v.size(); ++i)
    {
        if (i == v[j])
        {
            using std::iter_swap; iter_swap(i, begin);
            ++j;
            ++begin;
        }
    }
    return begin;
}

然后你可以像这样使用它:

int main() { std::vector<int> v; v.push_back(6); v.push_back(5); v.push_back(5); v.push_back(8); v.push_back(5); v.push_back(8); v.erase(uniquify(v.begin(), v.end()), v.end()); }

*
注意:

这是典型情况下的明智方法,其中重复项的数量不会太高。如需更全面的性能分析,请参阅相关问题的相关答案

快速简单,C++11:

14
投票
template<typename T> size_t RemoveDuplicatesKeepOrder(std::vector<T>& vec) { std::set<T> seen; auto newEnd = std::remove_if(vec.begin(), vec.end(), [&seen](const T& value) { if (seen.find(value) != std::end(seen)) return true; seen.insert(value); return false; }); vec.erase(newEnd, vec.end()); return vec.size(); }

int unsortedRemoveDuplicates(std::vector<int>& numbers)
{
    std::set<int> seenNums; //log(n) existence check

    auto itr = begin(numbers);
    while(itr != end(numbers))
    {
        if(seenNums.find(*itr) != end(seenNums)) //seen? erase it
            itr = numbers.erase(itr); //itr now points to next element
        else
        {
            seenNums.insert(*itr);
            itr++;
        }
    }

    return seenNums.size();
}


//3 6 3 8 9 5 6 8
//3 6 8 9 5

9
投票
为了验证所提出的解决方案的性能,我测试了其中的三个,如下所列。测试使用具有 1 mln 元素和不同重复比例(0%、1%、2%、...、10%、...、90%、100%)的随机向量。

5
投票

结果表明,如果您知道至少有 1% 的重复项,则带有 remove_if

std::set

 解决方案是最好的解决方案。否则,您应该采用
sort
解决方案:
// Intel(R) Core(TM) i7-2600 CPU @ 3.40 GHz 3.40 GHz
// 16 GB RAM, Windows 7, 64 bit
//
// cl 19
// /GS /GL /W3 /Gy /Zc:wchar_t /Zi /Gm- /O2 /Zc:inline /fp:precise /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /WX- /Zc:forScope /Gd /Oi /MD /EHsc /nologo /Ot 
//
// 1000 random vectors with 1 000 000 elements each.
// 11 tests: with 0%, 10%, 20%, ..., 90%, 100% duplicates in vectors.

// Ratio: 0
// set_copy_if   : Time : 618.162 ms +- 18.7261 ms
// set_remove_if : Time : 650.453 ms +- 10.0107 ms
// sort          : Time : 212.366 ms +- 5.27977 ms
// Ratio : 0.1
// set_copy_if   : Time : 34.1907 ms +- 1.51335 ms
// set_remove_if : Time : 24.2709 ms +- 0.517165 ms
// sort          : Time : 43.735 ms +- 1.44966 ms
// Ratio : 0.2
// set_copy_if   : Time : 29.5399 ms +- 1.32403 ms
// set_remove_if : Time : 20.4138 ms +- 0.759438 ms
// sort          : Time : 36.4204 ms +- 1.60568 ms
// Ratio : 0.3
// set_copy_if   : Time : 32.0227 ms +- 1.25661 ms
// set_remove_if : Time : 22.3386 ms +- 0.950855 ms
// sort          : Time : 38.1551 ms +- 1.12852 ms
// Ratio : 0.4
// set_copy_if   : Time : 30.2714 ms +- 1.28494 ms
// set_remove_if : Time : 20.8338 ms +- 1.06292 ms
// sort          : Time : 35.282 ms +- 2.12884 ms
// Ratio : 0.5
// set_copy_if   : Time : 24.3247 ms +- 1.21664 ms
// set_remove_if : Time : 16.1621 ms +- 1.27802 ms
// sort          : Time : 27.3166 ms +- 2.12964 ms
// Ratio : 0.6
// set_copy_if   : Time : 27.3268 ms +- 1.06058 ms
// set_remove_if : Time : 18.4379 ms +- 1.1438 ms
// sort          : Time : 30.6846 ms +- 2.52412 ms
// Ratio : 0.7
// set_copy_if   : Time : 30.3871 ms +- 0.887492 ms
// set_remove_if : Time : 20.6315 ms +- 0.899802 ms
// sort          : Time : 33.7643 ms +- 2.2336 ms
// Ratio : 0.8
// set_copy_if   : Time : 33.3077 ms +- 0.746272 ms
// set_remove_if : Time : 22.9459 ms +- 0.921515 ms
// sort          : Time : 37.119 ms +- 2.20924 ms
// Ratio : 0.9
// set_copy_if   : Time : 36.0888 ms +- 0.763978 ms
// set_remove_if : Time : 24.7002 ms +- 0.465711 ms
// sort          : Time : 40.8233 ms +- 2.59826 ms
// Ratio : 1
// set_copy_if   : Time : 21.5609 ms +- 1.48986 ms
// set_remove_if : Time : 14.2934 ms +- 0.535431 ms
// sort          : Time : 24.2485 ms +- 0.710269 ms

// Ratio: 0
// set_copy_if   : Time: 666.962 ms +- 23.7445 ms
// set_remove_if : Time: 736.088 ms +- 39.8122 ms
// sort          : Time: 223.796 ms +- 5.27345 ms
// Ratio: 0.01
// set_copy_if   : Time: 60.4075 ms +- 3.4673 ms
// set_remove_if : Time: 43.3095 ms +- 1.31252 ms
// sort          : Time: 70.7511 ms +- 2.27826 ms
// Ratio: 0.02
// set_copy_if   : Time: 50.2605 ms +- 2.70371 ms
// set_remove_if : Time: 36.2877 ms +- 1.14266 ms
// sort          : Time: 62.9786 ms +- 2.69163 ms
// Ratio: 0.03
// set_copy_if   : Time: 46.9797 ms +- 2.43009 ms
// set_remove_if : Time: 34.0161 ms +- 0.839472 ms
// sort          : Time: 59.5666 ms +- 1.34078 ms
// Ratio: 0.04
// set_copy_if   : Time: 44.3423 ms +- 2.271 ms
// set_remove_if : Time: 32.2404 ms +- 1.02162 ms
// sort          : Time: 57.0583 ms +- 2.9226 ms
// Ratio: 0.05
// set_copy_if   : Time: 41.758 ms +- 2.57589 ms
// set_remove_if : Time: 29.9927 ms +- 0.935529 ms
// sort          : Time: 54.1474 ms +- 1.63311 ms
// Ratio: 0.06
// set_copy_if   : Time: 40.289 ms +- 1.85715 ms
// set_remove_if : Time: 29.2604 ms +- 0.593869 ms
// sort          : Time: 57.5436 ms +- 5.52807 ms
// Ratio: 0.07
// set_copy_if   : Time: 40.5035 ms +- 1.80952 ms
// set_remove_if : Time: 29.1187 ms +- 0.63127 ms
// sort          : Time: 53.622 ms +- 1.91357 ms
// Ratio: 0.08
// set_copy_if   : Time: 38.8139 ms +- 1.9811 ms
// set_remove_if : Time: 27.9989 ms +- 0.600543 ms
// sort          : Time: 50.5743 ms +- 1.35296 ms
// Ratio: 0.09
// set_copy_if   : Time: 39.0751 ms +- 1.71393 ms
// set_remove_if : Time: 28.2332 ms +- 0.607895 ms
// sort          : Time: 51.2829 ms +- 1.21077 ms
// Ratio: 0.1
// set_copy_if   : Time: 35.6847 ms +- 1.81495 ms
// set_remove_if : Time: 25.204 ms +- 0.538245 ms
// sort          : Time: 46.4127 ms +- 2.66714 ms

这就是 WilliamKF 正在寻找的内容。它使用擦除语句。此代码适用于列表,但不适用于向量。对于向量,不应使用擦除语句。 

0
投票
//makes uniques in one shot without sorting !! template<class listtype> inline void uniques(listtype* In) { listtype::iterator it = In->begin(); listtype::iterator it2= In->begin(); int tmpsize = In->size(); while(it!=In->end()) { it2 = it; it2++; while((it2)!=In->end()) { if ((*it)==(*it2)) In->erase(it2++); else ++it2; } it++; } }

我在不使用排序的情况下尝试过的向量是:

//makes vectors as fast as possible unique template<typename T> inline void vectoruniques(std::vector<T>* In) { int tmpsize = In->size(); for (std::vector<T>::iterator it = In->begin();it<In->end()-1;it++) { T tmp = *it; for (std::vector<T>::iterator it2 = it+1;it2<In->end();it2++) { if (*it2!=*it) tmp = *it2; else *it2 = tmp; } } std::vector<T>::iterator it = std::unique(In->begin(),In->end()); int newsize = std::distance(In->begin(),it); In->resize(newsize); }

不知怎的,这看起来像是可行的。我测试了一下,也许有人可以告诉我这是否真的有效! 该解决方案不需要任何更大的运算符。我的意思是为什么使用更大的运算符来搜索独特的元素? 向量的用法:

int myints[] = {21,10,20,20,20,30,21,31,20,20,2}; std::vector<int> abc(myints , myints+11); vectoruniques(&abc);

这里有一些可以处理 POD 和非 POD 类型并支持移动的东西。使用默认运算符 == 或自定义相等谓词。不需要排序/运算符

0
投票
template <typename Cnt, typename _Pr = std::equal_to<typename Cnt::value_type>> void remove_duplicates( Cnt& cnt, _Pr cmp = _Pr() ) { Cnt result; result.reserve( std::size( cnt ) ); // or cnt.size() if compiler doesn't support std::size() std::copy_if( std::make_move_iterator( std::begin( cnt ) ) , std::make_move_iterator( std::end( cnt ) ) , std::back_inserter( result ) , [&]( const typename Cnt::value_type& what ) { return std::find_if( std::begin( result ) , std::end( result ) , [&]( const typename Cnt::value_type& existing ) { return cmp( what, existing ); } ) == std::end( result ); } ); // copy_if cnt = std::move( result ); // place result in cnt param } // remove_duplicates

<, key generation, or a separate set. No idea if this is more efficient than the other methods described above.

使用/测试:

{ std::vector<int> ints{ 0,1,1,2,3,4 }; remove_duplicates( ints ); assert( ints.size() == 5 ); } { struct data { std::string foo; bool operator==( const data& rhs ) const { return this->foo == rhs.foo; } }; std::vector<data> mydata{ { "hello" }, {"hello"}, {"world"} } , mydata2 = mydata ; // use operator== remove_duplicates( mydata ); assert( mydata.size() == 2 ); // use custom predicate remove_duplicates( mydata2, []( const data& left, const data& right ) { return left.foo == right.foo; } ); assert( mydata2.size() == 2 ); }

您可以声明一个用 -1 初始化的大全局或静态大数组(在我的示例中为 t0)并使用它。

0
投票
#include <vector> #include <stdlib.h> #include <algorithm> #include <iostream> static std::vector<int> t0s (1000000000, -1); int main (int argc, char* argv []) { //random init vec const int L (10), N (5); std::vector<int> vec (L, 0); std::for_each (vec.begin (), vec.end (), [&N] (auto& s) {s = rand () %N;}); std::cout << "\tvec == "; std::for_each (vec.begin (), vec.end (), [] (const auto& s) {std::cout << s << " ";}); std::cout << std::endl; auto& t0 (t0s); const auto zero ((const int) 0), sm1 ((const int) -1); auto i (vec.begin ()); auto second ((int) 0); std::for_each (vec.begin (), vec.end (), [&t0, &i, &second, &sm1] (const int& s) { if (t0 [s] == sm1) t0 [(*i++ = s)] = zero;

, }); if (i != vec.end()) vec.erase(i, vec.end()); std::cout
//leaving t0 clean
std::for_each (vec.begin (), i, [&t0, &sm1] (const auto& s) {t0 [s] = sm1;});
}

<< "\tvec same order but unique == "; std::for_each (vec.begin (), vec.end (), [] (const auto& s) {std::cout << s << " ";});

正常输出:
vec == 3 2 0 3 0 1 2 42
vec same order but unique == 3 2 1 0 4

这是一个 c++11 通用版本,它与迭代器一起使用并且不分配额外的存储。它可能有 O(n^2) 的缺点,但对于较小的输入大小可能会更快。

-1
投票
template<typename Iter> Iter removeDuplicates(Iter begin,Iter end) { auto it = begin; while(it != end) { auto next = std::next(it); if(next == end) { break; } end = std::remove(next,end,*it); it = next; } return end; }

....

std::erase(removeDuplicates(vec.begin(),vec.end()),vec.end());

示例代码:
http://cpp.sh/5kg5n

© www.soinside.com 2019 - 2024. All rights reserved.