我认为问题已经很清楚了。我有一个唯一项目的单向链表(forward_list),想要从中删除一个项目:
std::forward_list<T> mylist;
// fill with stuff
mylist.remove_if([](T const& value)
{
return value == condition;
});
我的意思是,这种方法虽然有效,但效率不高,因为一旦找到并删除该项,它仍会继续搜索。有更好的方法吗?还是我需要手动完成?
我认为问题已经很清楚了。我有一个唯一项目的单向链表(forward_list),想要从中删除一个项目:
std::forward_list<T> mylist;
// fill with stuff
mylist.remove_if([](T const& value)
{
return value == condition;
});
我的意思是,这种方法虽然有效,但效率不高,因为一旦找到并删除该项,它仍会继续搜索。有更好的方法吗?还是我需要手动完成?
std::adjacent_find
函数,并在之后使用erase_after
成员函数。#include <algorithm>
#include <cassert>
#include <forward_list>
#include <iostream>
#include <ios>
#include <iterator>
// returns an iterator before first element equal to value, or last if no such element is present
// pre-condition: before_first is incrementable and not equal to last
template<class FwdIt, class T>
FwdIt find_before(FwdIt before_first, FwdIt last, T const& value)
{
assert(before_first != last);
auto first = std::next(before_first);
if (first == last) return last;
if (*first == value) return before_first;
return std::adjacent_find(first, last, [&](auto const&, auto const& R) {
return R == value;
});
}
int main()
{
auto e = std::forward_list<int>{};
std::cout << std::boolalpha << (++e.before_begin() == end(e)) << "\n";
std::cout << (find_before(e.before_begin(), end(e), 0) == end(e)) << "\n";
auto s = std::forward_list<int>{ 0 };
std::cout << (find_before(s.before_begin(), end(s), 0) == s.before_begin()) << "\n";
auto d = std::forward_list<int>{ 0, 1 };
std::cout << (find_before(d.before_begin(), end(d), 0) == d.before_begin()) << "\n";
std::cout << (find_before(d.before_begin(), end(d), 1) == begin(d)) << "\n";
std::cout << (find_before(d.before_begin(), end(d), 2) == end(d)) << "\n";
// erase after
auto m = std::forward_list<int>{ 1, 2, 3, 4, 1, 3, 5 };
auto it = find_before(m.before_begin(), end(m), 3);
if (it != end(m))
m.erase_after(it);
std::copy(begin(m), end(m), std::ostream_iterator<int>(std::cout, ","));
}
一旦找到匹配项,它将停止。请注意,adjacent_find
需要一个二进制谓词,并且通过仅比较第二个参数,我们可以得到要删除的元素之前的迭代器,以便erase_after
可以实际删除它。复杂度为O(N)
,因此您不会比这更有效率。
adjacent_find
函数(我甚至不知道这个函数存在)。点赞👍 - Angew is no longer proud of SO<
(而不是 <=
,就像 std::is_sorted
一样)。所以它在工作记忆中还比较“新鲜”。 - TemplateRexadjacent_find
以达到你想要的效果时,却在给其他答案投反对票?这很新奇... - Nimerase_after
函数:“要求:位置后面的迭代器是可解引用的”。空列表具有 begin() == end()
,解引用 end()
(或任何无效)迭代器会导致未定义行为。 - TemplateRexadjacent_find
的黑客攻击。前向迭代器天生就适合被 adjacent_find
处理,因为你真的需要一直进行一个向前看一步的操作,因为你不能以 O(1)
的时间复杂度向后查找。 - TemplateRex顺便说一下,这是另一个简短的版本
template< typename T, class Allocator, class Predicate >
bool remove_first_if( std::forward_list< T, Allocator >& list, Predicate pred )
{
auto oit = list.before_begin(), it = std::next( oit );
while( it != list.end() ) {
if( pred( *it ) ) { list.erase_after( oit ); return true; }
oit = it++;
}
return false;
}
it=list.begin()
作为初始化更明确一些,然后在循环结束时加上 oit=it,++it;
,但这显然与您编写的代码等效。 - Marc van Leeuwen你需要自己动手...
template <typename Container, typename Predicate>
void remove_first_of(Container& container, Predicate p)
{
auto it = container.before_begin();
for (auto nit = std::next(it); ; it = nit, nit = std::next(it))
{
if (nit == container.end())
return;
if (p(*nit))
{
container.erase_after(it);
return;
}
}
}
一个更完整的示例...
erase_after
! - Nim标准库中没有直接适用的内容。实际上有。请查看@TemplateRex的答案。
如果您想将搜索与删除结合起来,也可以自己编写,类似于以下内容:
template <class T, class Allocator, class Predicate>
bool remove_first_if(std::forward_list<T, Allocator> &list, Predicate pred)
{
auto itErase = list.before_begin();
auto itFind = list.begin();
const auto itEnd = list.end();
while (itFind != itEnd) {
if (pred(*itFind)) {
list.erase_after(itErase);
return true;
} else {
++itErase;
++itFind;
}
}
return false;
}
erase_after
。我已经反映了您使用纯std
解决方案的答案,但如果您发现自己一遍又一遍地执行erase_before(find_before_first())
,那么您可能也想将此组合封装在一个函数中。 - Angew is no longer proud of SOerase_after()
调用一起封装,那么您必须传递容器。是的,这个函数可以在内部调用您的find_before_first()
(这样会更短),但是它仍然可能有存在的有效原因。 - Angew is no longer proud of SOstd
库、Nim 的通用容器和我的 forward_list
。我认为这是一个不错的结果 :-) - Angew is no longer proud of SO当我在80年代初学习编程时,这种事情曾经是标准的练习。回忆解决方案并将其与C++进行比较可能很有趣。实际上,那时使用的是Algol 68,但我不会强制让您使用Algol 68,而是给出C语言的翻译。给定:
typedef ... T;
typedef struct node *link;
struct node { link next; T data; };
如果想要删除第一个节点,就需要传递列表头指针的地址,因此可以写成:
void search_and_destroy(link *p_addr, T y)
{
while (*p_addr!=NULL && (*p_addr)->data!=y)
p_addr = &(*p_addr)->next;
if (*p_addr!=NULL)
{
link old = *p_addr;
*p_addr = old->next; /* unlink node */
free(old); /* and free memory */
}
}
这里有很多关于*p_addr
的出现;它最后一次出现时是一个赋值语句的左值,这也是为什么首先需要指针地址的原因。请注意,尽管看起来很复杂,语句p_addr = &(*p_addr)->next;
只是用指针替换它所指向的值,然后添加一个偏移量(这里是0)。
可以引入一个辅助指针value来简化代码,如下所示:
void search_and_destroy(link *p_addr, T y)
{
link p=*p_addr;
while (p!=NULL && p->data!=y)
p=*(p_addr = &p->next);
if (p!=NULL)
{
*p_addr = p->next;
free(p);
}
}
*p_addr
在第一个示例中连续使用多次,并将其保存在寄存器中。std::forward_list<T>
,我们不允许访问链接节点的指针,而是得到那些笨拙的“迭代器指向真正操作之前的一个节点”。我们的解决方案变成了:void search_and_destroy(std::forward_list<T> list, T y)
{
std::forward_list<T>::iterator it = list.before_begin();
const std::forward_list<T>::iterator NIL = list.end();
while (std::next(it)!=NIL && *std::next(it)!=y)
++it;
if (std::next(it)!=NIL)
list.erase_after(it);
}
std::next(it)
,而不必每次都拼写它(不要忘记在增加it
时刷新其值),从而基本上得到Daniel Frey的答案。(我们也可以尝试将该变量作为指向类型*T
的指针等于&*std::next(it)
,这对我们所做的使用足够了,但实际上确保它在std::next(it)==NIL
时成为空指针有点麻烦,因为标准不允许我们取&*NIL
)。
我不能不感觉自从早期以来,这个问题的解决方案并没有变得更加优雅。
return value == condition;
。 - Geoffroystd::find_first_of
- P0Wadjacent_find
后跟erase_after
就可以解决问题了,详见我的回答。 - TemplateRex