像时尚一样在STL中删除邻居

时间:2014-03-04 20:48:22

标签: c++ algorithm stl

我试图以STL方式实现具有以下功能的算法:

给定范围[first,last),邻域范围nSpan和(二进制)谓词Pred,它会删除该范围内的元素,因此Pred是对于最多彼此远离的任何剩余元素{1}}

,情况并非如此

示例

  • nSpan = 1且nSpan = Equality =>衰减到Pred算法
  • std::unique = 2且nSpan = PointEquality =>消毒折线

    Pred
  • + P2 | v ^ P0 | P4 P0 P1 P4 +---->-------+---->------+ becomes +---->-------+---->------+ P1 P3 = 2和nSpan =平等:['a','b','a','d','e','d','f'] - &gt ; ['a','d','f']

在最后一个例子中,显而易见的是(为了防止算法变得模棱两可),我们从第一个迭代器扫描并打开,同时检查nSpan距离以删除元素(否则会有多种方法来删除元素)。

到目前为止我的尝试(下面的代码清单)有以下缺点:

  • 第一次扫描后,新范围可能包含无效的新元素,因此需要递归功能(再次从新开始向末尾扫描)重新扫描范围(或者可能是模仿的)每次删除都会递归)
  • 它没有作为Pred函数实现,而是作为remove函数实现(我需要删除一个,而且看起来要困难得多)并强制提供整体容器作为参数而不是范围(理想情况下算法应该是容器不可知的)

我正在列出first attempt

erase

理想签名

    template<typename Cont, typename It, class Pr>
    void erase_neighbors(Cont &cont, It first, It last, int nSpan, Pr Pred)
    {
        if (0 < nSpan && nSpan < std::distance(first, last)) for (It it2; (it2 = first), first != last; )
        {
            if (nSpan < std::distance(it2, last))
            {
                std::advance(it2, nSpan);
                if (Pred(*first, *it2))
                {
                    first = cont.erase(first, it2);
                    last = cont.end();
                    continue;
                }
            }
            ++first;
        }
    }

理想的实现:非c ++ 11且没有提升(即使有相关的提升算法我会很高兴知道它)

2 个答案:

答案 0 :(得分:2)

在某种程度上我理解问题陈述,这似乎做你想要的。见in action

template<typename It, class Pr>
It remove_neighbors(It first, It last, int nSpan, Pr Pred) {
  if (first == last || nSpan <= 0) return last;

  It lastGood = first;
  It cur = first;
  ++cur;
  for (; cur != last; ++cur) {
    bool found = false;
    It back = lastGood;
    for (int i = nSpan; i > 0; --i, --back) {
      if (Pred(*back, *cur)) {
        found = true;
        lastGood = back;
        break;
      }
      if (back == first) break;
    }

    if (!found) {
      ++lastGood;
      *lastGood = std::move(*cur);
    }
  }
  ++lastGood;
  return lastGood;
}

这不会超过N移动/副本,也不会超过N * nSpan Pred次调用。

答案 1 :(得分:1)

通过维护下一个元素的表,可以避免列出的问题。因此,每个位置都会指向符合good邻居的下一个有效位置,而不会违反谓词,如下所示:

map<unsigned, unsigned> neigh_table;
while(it != end){
    neigh = startneigh = it + 1;
    do{
        if(pred(it, neigh)) //if predicate fails, restart with a new neighbour
            neigh = startneigh = neigh + 1;
        else
            ++neigh;
    }while(neigh - startneigh < range && neigh != end);

    neigh_table[it-start] = startneigh - start;
    it = neigh;
}

在操作结束时,您可以:

  1. 返回邻居的查找表以供用户处理或
  2. 返回一个迭代器列表,它与容器分开,就像一个矢量/邻居迭代器列表,通过在函数内部自己遍历映射。
  3. 在任何一种情况下,如果不将实际容器传递给函数,您将无法修改容器。这就是stl::remove等函数不会修改容器长度的原因。有关如何使用stl :: remove实际修改容器的示例,请参阅remove-erase idiom