我需要采取一个c++向量与潜在的很多元素,擦除重复,并排序。

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

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

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

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

或者是否有另一种(也许更有效的)方法来完成这一切?


当前回答

大部分答案似乎是使用O(nlogn),但使用unordered_set,我们可以将其减少到O(n)。我看到了一些使用集合的解决方案,但我发现了这个解决方案,使用集合和迭代器似乎更优雅。

using Intvec = std::vector<int>;

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

其他回答

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

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

也不要只关注时间效率。Sort+unique+erase操作在O(1)空间,而set构造操作在O(n)空间。而且这两种方法都不能直接用于map-reduce并行化(对于真正庞大的数据集)。

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

unique只删除重复的元素,如果它们是邻居:你必须先对向量排序,然后它才能像你想的那样工作。

unique被定义为稳定的,所以在vector上运行unique之后,它仍然会被排序。

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

action::unique(vec);

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

不幸的是,动作在c++ 20中没有标准化,因为即使在c++ 20中,范围库的其他部分仍然必须使用原始库。

取决于用例。如果你期望有少于100个正整数的唯一值,并且你有一个cpu能够处理avx512f指令集,那么你可以以每个元素15个时钟周期或每秒3 -5亿个插入的速度插入元素,通过与一个小型查找表进行简单的比较。

接下来的实现使用CPU寄存器对~50个惟一值进行值查找,并对~1000个惟一值进行L1缓存。对于L1缓存版本,每次插入大约需要160个时钟周期,这相当于大约每秒25M个插入,并且比使用std::set慢。对于只有4个唯一值,它以每个元素5.8个周期的速率插入,高于500M/s。

//g++  7.4.0
// time measurement taken from another answer
// valid C99 and C++

#include <stdint.h>  // <cstdint> is preferred in C++, but stdint.h works.

#ifdef _MSC_VER
# include <intrin.h>
#else
# include <x86intrin.h>
#endif

// optional wrapper if you don't want to just use __rdtsc() everywhere
inline
uint64_t readTSC() {
     _mm_lfence();  // optionally wait for earlier insns to retire before reading the clock
    uint64_t tsc = __rdtsc();
     _mm_lfence();  // optionally block later instructions until rdtsc retires
    return tsc;
}

// requires a Nehalem or newer CPU.  Not Core2 or earlier.  IDK when AMD added it.
inline
uint64_t readTSCp() {
    unsigned dummy;
    return __rdtscp(&dummy);  // waits for earlier insns to retire, but allows later to start
}



#include <iostream>

template<int n>
struct FastUnique
{
    public:
    FastUnique()
    {
         it=0;
         for(int i=0;i<n;i++)
             dict[i]=-1;
    }

    void insert(const int val)
    {
        if(!test(dict,val))
            dict[it++]=val;
    }

    const int get(const int index)
    {
        return dict[index];
    }

    const int size()
    {
        return it;
    }

    private:
    int dict[n];
    int it;
    bool test(const int * dict, const int val)
    {
        int c=0;
        for(int i=0;i<n;i++)
            c+=(dict[i]==val);
        return c>0;
    }
};

int main()
{
    std::cout << "Hello, world!\n";
    const int n=500000000;

    FastUnique<64> fastSet;

    auto t= readTSC();

    for(int i=0;i<n;i++)
        fastSet.insert(i&63);

    auto t2=readTSC();

    std::cout<<(t2-t)/(double)n<<"cycles per iteration"<<std::endl;
   
    for(int i=0;i<fastSet.size();i++)
        std::cout<<fastSet.get(i)<<std::endl;
    
    return 0;
}