循环在 std::list 上为迭代的 std::erase 挂起

Posted

技术标签:

【中文标题】循环在 std::list 上为迭代的 std::erase 挂起【英文标题】:loop hangs for iterated std::erase on std::list 【发布时间】:2020-01-30 23:32:30 【问题描述】:

我正在尝试使用哈希表删除存储在列表中的整数向量的重复组合。遍历列表中的每个整数向量,I:

    计算 hash_value (thash) 查看哈希值是否已经在哈希表(pids)中 如果它在哈希表中,请从列表中删除该向量。 否则,将该值添加到 hash_table 并增加列表 迭代器

打印语句似乎证实了我的逻辑,但循环在迭代的第四步挂起。我已经评论了导致问题的it++vz.remove(it),并且只在下面的代码中显示了逻辑。代码也可以通过ideone获得:https://ideone.com/JLGA0f

    #include<iostream>
    #include<vector>
    #include<list>
    #include<cmath>
    #include<unordered_set>
    using namespace std;

    double hash_cz(std::vector<int> &cz, std::vector<double> &lprimes) 
      double pid = 0;
      for(auto it = cz.begin(); it != cz.end(); it++) 
        pid += lprimes[*it];
      
      return pid;
    

    int main()
      // create list of vectors
      std::list<std::vector<int>> vz;
      vz.push_back(2,1);
      vz.push_back(1,2);
      vz.push_back(1,3);
      vz.push_back(1,2,3);
      vz.push_back(2, 1);

      // vector of log of prime numbers
      std::vector<double> lprimes 2, 3, 5, 7;
      for (auto it = lprimes.begin(); it != lprimes.end(); it++) 
        *it = std::log(*it);
      

      std::unordered_set<double> pids;
      double thash;
      for (auto it = vz.begin(); it != vz.end(); ) 
        thash = hash_cz(*it, lprimes);
        std::cout << thash << std::endl;
        // delete element if its already been seen
        if (pids.find(thash) != pids.end()) 
           std::cout << "already present. should remove from list" << std::endl;
           // vz.erase(it);
        
        else 
          // otherwise add it to hash_table and increment pointer
          std::cout << "not present. add to hash. keep in list." << std::endl;
          pids.insert(thash);
          // it++;
        
        it++;
      

      for (auto it = vz.begin(); it != vz.end(); it++) 
        for (auto j = it -> begin(); j != it -> end(); j++) 
          std::cout << *j << ' ';
        
        std::cout << std::endl;
      
      return 0;
    

【问题讨论】:

【参考方案1】:

问题是这行代码:

vz.erase(it);

它将迭代器保持在原来的位置,即使其无效。它应该是:

vz.erase(it++);

it = vz.erase( it );

注意:std::unoredered_set::insert() 返回值告诉你插入是否成功(如果相同的值元素已经存在),你应该调用它并检查结果。在您的代码中,您会进行两次查找:

if (pids.insert(thash).second )  
    // new element added
    ++it;
 else  
    // insertion failed, remove 
    it = vz.erase( it );

由于std::list 提供remove_if(),您的代码可以简化:

vz.remove_if( [&pids,&lprimes]( auto &v )  
   return !pids.insert( hash_cz(v, lprimes) ).second );
 );

而不是整个循环。

【讨论】:

优秀的答案。非常感谢你的帮助。并感谢有关插入 unordered_sets 的提示。【参考方案2】:

如果元素已经被看到,你擦除()it 节点,然后在循环结束时增加it:未定义的行为。改用erase(it++)。

如果尚未看到该元素,则递增it,然后在for 的末尾再次执行此操作,如果itend() - 1,则在它移过末尾时产生UB。

【讨论】:

如此简单。感谢您的澄清!

以上是关于循环在 std::list 上为迭代的 std::erase 挂起的主要内容,如果未能解决你的问题,请参考以下文章

在基于范围的 for 循环期间插入 std::list 的后面

如何使用 OpenMP 通过 C++ std::list 并行化 for 循环?

给定迭代器替换 std::list 对象

如何使用迭代器擦除 std::list 模板

我可以在 std::list 中移动元素而不会使迭代器或引用无效,但是如何?

迭代一个基于范围的临时 std::initializer_list for