8

好吧,我认为这个问题几乎可以概括。我有一个独特项目的 forward_list,并想从中删除一个项目:

std::forward_list<T> mylist;
// fill with stuff

mylist.remove_if([](T const& value)
  {
    return value == condition;
  });

我的意思是,这种方法工作正常,但效率低下,因为一旦找到并删除该项目,它就会继续搜索。有更好的方法还是我需要手动完成?

4

5 回答 5

11

如果您只想删除第一个匹配项,您可以使用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)你不会得到比这更有效的。

于 2013-10-15T07:33:22.667 回答
2

FWIW,这是另一个简短的版本

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;
}
于 2013-10-15T08:32:19.067 回答
1

标准库中没有可以直接适用的内容。实际上,有。请参阅@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;
}
于 2013-10-15T07:36:57.117 回答
1

当我在 80 年代初学习编程时,这种东西曾经是标准练习。回忆一下这个解决方案可能会很有趣,并将其与 C++ 中可以做的事情进行比较。实际上那是在 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;它是最后一个,它是赋值的 LHS,这就是首先需要指针地址的原因。请注意,尽管有明显的复杂性,但该语句p_addr = &(*p_addr)->next;只是将指针替换为其指向的值,然后添加一个偏移量(此处为 0)。

可以引入一个辅助指针来减轻代码的负担,如下所示

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)。

我不禁感到,自古以来,这个问题的解决方案并没有变得更加优雅。

于 2014-06-20T20:43:59.020 回答
1

将不得不推出自己的...

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

一个更完整的例子......

于 2013-10-15T07:44:20.267 回答