remove_neighbors以类似STL的方式

remove_neighbors in STL like fashion

本文关键字:STL 方式 neighbors remove      更新时间:2023-10-16

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

给定一个范围[first,last)、一个邻域跨度nSpan和一个(二进制)谓词Pred,它会从该范围中删除元素,因此对于最多相距nSpan的任何剩余元素,Pred都不成立

示例

  • nSpan=1和Pred=Equality=>衰减到std::unique算法
  • nSpan=2和Pred=PointEquality=>清除多段线

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

在最后一个例子中,很明显(为了防止算法变得模糊),我们从第一个迭代器开始扫描,同时检查nSpan距离以删除元素(否则将有多种方法删除元素)。

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

  • 第一次扫描后,新范围可能有无效的新元素,因此需要递归功能(再次从新开始扫描到结束)来重新扫描范围(或者每次删除时都可能启动递归)
  • 它不是作为remove函数实现的,而是作为erase函数实现的(我需要一个remove函数,这似乎要困难得多),这迫使我们将整个容器作为参数而不是范围来提供(理想情况下,算法应该与容器无关)

我列出了第一次尝试

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

理想签名

template<typename It, class Pr>
It remove_neighbors(It first, It last, int nSpan, Pr Pred);

理想的实现:非c++11且没有boost(即使有相关的boost算法,我也很乐意了解它)

在我理解问题陈述的范围内,这似乎符合您的要求。看看它在行动:

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移动/复制次数不超过,Pred调用次数不超过N * nSpan

您可以通过维护下一个元素的表来避免列出的这两个问题。因此,在不违反谓词的情况下,每个位置都将指向符合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. 通过在函数内自己遍历映射,返回一个迭代器列表,该列表与容器分离,就像是邻居迭代器的向量/列表一样

无论哪种情况,如果不将实际容器传递给函数,都无法修改容器。这就是像stl::remove这样的函数不修改容器长度的原因。有关如何使用stl::remove实际修改容器的示例,请参阅remove-erase习惯用法。