找到向量中所有具有相同值的元素,然后将它们全部从向量中删除

Find all elements with the same value in a vector, then erase all of them out of the vector

问题是我需要在向量中找到所有具有相同值的元素,对它们做一些处理,然后从向量中删除所有元素。继续这样做,直到向量为空。

vector<unsigned> L;
vector<unsigned>::iterator it, it2, it3;
vector<unsigned> vec;
unsigned Z;

// populate the vector (1, 2, 3, 4, 2, 4)
for(unsigned i = 1; i <= 4; ++i)
   L.push_back(i);
for(unsigned i = 2; i <= 4; i = i + 2)
   L.push_back(i);

it = L.begin();
while(it != L.end() -1){
  cout<< "*it = " << *it << endl;
  Z=0;
  vec.clear();

  it2 = it + 1;
  cout<< "*it2 = " << *it2 << endl;

  while(it2 != L.end()){
     cout << "Loop *it2 = " << *it2 <<endl;
     if(*it == *it2){
        vec.push_back(*it);
        L.erase(it2);  // iterator automatically points to the next element
        cout<< "after erase(it2), *it2 = " << *it2 << endl;
        continue;
     }
     ++it2;
  }
// do something (here I calculate the average)
  for(it3 = vec.begin(); it3 != vec.end(); ++it3)
     Z = Z+ *it3;

  Z= Z/vec.size();

  cout<< "Z = " << Z << endl << endl;
  L.erase(it); // iterator automatically points to the next element
}

输出为:

*it = 1
*it2 = 2
Loop *it2 = 2
Loop *it2 = 3
Loop *it2 = 4
Loop *it2 = 2
Loop *it2 = 4

然后它停止工作

如果我用这段代码填充向量

// populate the vector (1, 2, 3, 4, 1, 3)
for(unsigned i = 1; i <= 4; ++i)
   L.push_back(i);
for(unsigned i = 1; i <= 4; i = i + 2)
   L.push_back(i);

那么输出就是

*it = 1
*it2 = 2
Loop *it2 = 2
Loop *it2 = 3
Loop *it2 = 4
Loop *it2 = 1
after erase(it2), *it2 = 3
Loop *it2 = 3
Z = 1

*it = 2
*it2 = 3
Loop *it2 = 3
Loop *it2 = 4
Loop *it2 = 3

它在这里停止工作

我知道第二个 while 循环有问题,但我不知道是什么。任何帮助将不胜感激。

问题出在您的 erase 电话上。 erase 不会自动更新迭代器;它 returns 指向下一个元素的迭代器。要修复它,您需要使用

it2 = L.erase(it2);

以后

it = L.erase(it);

如果目标是

  1. 处理重复然后
  2. 擦除它们

有一个更简单的解决方案,那就是使用 std::stable_partition,以及 std::set:

#include <algorithm>
#include <vector>
#include <iterator>
#include <set>
#include <iostream>
#include <numeric>

using namespace std;

int main()
{
    std::vector<int> L = { 1, 2, 4, 3, 2, 4 };
    std::set<int> tempSet;
    //...
    // partition the elements, unique items on left, duplicates on right
    auto divider = stable_partition(L.begin(), L.end(), [&](int n)
    {
        // return true if item is not in the set, false otherwise
        return tempSet.insert(n).second;
    });

    // do something with the duplicates, for example, print them
    cout << "Here are the dups:\n";
    copy(divider, L.end(), ostream_iterator<int>(cout, " "));

    // get the average

    // get number of duplicates  
    size_t numDups = std::distance(divider, L.end());  
    double avg = 0.0;

    // compute average using std::accumulate
    if ( numDups > 0 ) 
       avg = std::accumulate(divider, L.end(), 0.0) / numDups;
    cout << "\nThe average of the duplicates is: " << avg << "\n";

    // erase the duplicates
    L.erase(divider, L.end());

    // print the updated vector now
    cout << "\n\nHere is the resulting vector:\n";
    copy(L.begin(), L.end(), ostream_iterator<int>(cout, " "));
}

Live Example, C++ 14

这是相同的代码,但使用 C++ 03(对于没有 C++11 / 14 的代码):

Live Example, C++ 03

注意上面的代码中没有循环。一切都由算法函数完成。分区、平均计算、擦除等,都是在没有循环的情况下执行的。

基本上我们测试每个项目以查看该项目是否存在于集合中。如果是,那么它将转到分区的右侧,如果不是,则该项目将转到分区的左侧。

return 值 divider 是分区中 "dividing line" 元素的迭代器。一旦项目被分区,我们就可以通过使用 divider 告诉我们 "good" 项目在哪里以及 "about to be erased" 项目在哪里来轻松处理它们。

顺便说一句,这在我第一次成功编译它时就起作用了——"luck" 让它快速运行的一个主要原因是算法在给定正确的参数(和如果谓词函数编写正确)。在一个容器中,尤其是vector这样的序列容器中,擦除、移动等项,几乎都是用1、2、3个算法函数来覆盖。

vector<unsigned> L;
vector<unsigned>::iterator it, it2, it3;
vector<unsigned> vec;
unsigned Z;

// populate the vector (1, 2, 3, 4, 2, 4)
for(unsigned i = 1; i <= 4; ++i)
    L.push_back(i);
for(unsigned i = 2; i <= 4; i = i + 2)
    L.push_back(i);

std::set<unsigned int> myset;
for(it = L.begin(); it != L.end(); ++it){
    myset.insert(*it);
}

for(std::set<unsigned int>::iterator iter = myset.begin();
    iter != myset.end(); ++iter){
    std::cout << "element = " << *iter << std::endl;
    Z += *iter;
}
std::cout << "sum = " << Z << std::endl;
Z = Z/myset.size();

std::cout<< "Average value = " << Z << std::endl;
L.clear();

这是我的解决方案,其灵感来自 PaulMcKenzie 的代码(请不要介意我重复使用了您的某些代码行)。就像他一样,我没有使用任何手写循环,而是使用了STL的算法,这是人们应该首先考虑的。

在我的解决方案中,我只使用了另一个完全相同类型的容器和两个简单​​的算法:std::remove_ifstd::find。这样代码使意图更清晰一点:找到重复项并删除它们。另请注意 std::move 的使用,当容器包含比 int 更复杂的东西时,它应该派上用场。在那种情况下,当然可以考虑使用 std::find_if

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

int main()
{
    std::vector<int> L = { 1, 2, 4, 3, 2, 4 };
    using cont = decltype(L);
    using vt = cont::value_type;

    // find duplicates and move them to separate container
    cont D;
    D.reserve(L.size());
    D.swap(L);
    D.erase(std::remove_if(D.begin(), D.end(), [&L] (vt& value)
    {
        if (L.cend() == std::find(L.cbegin(), L.cend(), value))
        {
            L.emplace_back(std::move(value));
            return true;
        }
        return false;
    }), D.end());

    // do something with the duplicates, for example, print them
    std::cout << "Here are the dups:\n";
    std::copy(D.cbegin(), D.cend(), std::ostream_iterator<vt>(std::cout, " "));

    // print the vector now
    std::cout << "\n\nHere is the resulting vector:\n";
    std::copy(L.begin(), L.end(), std::ostream_iterator<vt>(std::cout, " "));

    return 0;
}

查看实际效果 here

我终于找到了我自己问题的解决方案。问题是

  1. 我们有一个包含许多重复元素的向量 L
  2. 我们需要找到所有的 dup 元素
  3. 对 dup 元素做点什么

我的方法是在 L 上使用 2 个迭代器,比如 itit2it 保留在 L 的开头,而 it2 将通过向量。如果 *it*it2 具有相同的值,那么我们将把 *it2 放入一个临时向量 vec 中。然后,我们L.erase(it2)it2会一直移动到L结束,将所有dup元素收集到vec中,并从L中删除它们。当it2到达L的末尾时,我们调用L.erase(it)。之后,该过程继续,直到向量 L 为空。

这是我修改的代码。最需要注意的是while中的条件。我使用 while(it < L.end()) 而不是 while(it != L.end()) 因为当我们删除一个元素时,迭代器会以某种方式通过条件 !=.

我正在使用旧编译器(早于 C++0x),因为我现在不想弄乱环境。因此,任何人都可以安全地使用我的代码。

vector<unsigned> L;
vector<unsigned>::iterator it, it2, it3, it4;
vector<unsigned> vec; //temporary variable to store matched elements
float Z;

// populate the vector (1, 2, 3, 4, 1, 3)
for(unsigned i = 1; i <= 4; ++i)
  L.push_back(i);
for(unsigned i = 1; i <= 4; i = i + 2)
  L.push_back(i);


it = L.begin();
while(it < L.end()){
  cout<< "*it = " << *it << endl;
  Z=0;
  vec.clear();

  vec.push_back(*it);
  if(L.size() == 1){
     cout << "Last element of vector = " << *it <<endl;
     goto Loop1;
  }
  else
     it2 = it + 1;

  while(it2 < L.end()){
     cout << "Loop *it2 = " << *it2 <<endl;   //debug
     if(*it == *it2){
        vec.push_back(*it2);
        L.erase(it2);  // iterator automatically points to the next element
        cout<< "after erase(it2), *it2 = " << *it2 << endl;   //debug
        continue;
     }
     ++it2;
  }

  Loop1:

  for(it3 = vec.begin(); it3 != vec.end(); ++it3)
     Z = Z+ *it3;

  Z= Z/vec.size();

  cout<< "Z = " << Z << endl ;

  if(L.empty()) break;

  L.erase(it); // iterator automatically points to the next element

  //debug
  cout<< endl <<  "new vector = "; 
  for(it4 = L.begin(); it4 != L.end(); ++it4) 
     cout << *it4 << ' ';
  cout << endl;
}

@PaulMcKenzie:我花了一段时间才了解您在 C++14 中的写作风格以及我不熟悉的 STL 函数的使用。您的代码水平很高,初学者很难。无论如何,我选择它作为您贡献的最佳答案。我真的从你那里学到了很多东西。感谢您的快速回复。