我如何有效地从 forward_list 中删除_if 只有一个元素?

Posted

技术标签:

【中文标题】我如何有效地从 forward_list 中删除_if 只有一个元素?【英文标题】:How do I efficiently remove_if only a single element from a forward_list? 【发布时间】:2013-10-15 07:22:17 【问题描述】:

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

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

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

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

【问题讨论】:

你可以在你的 lambda 中简单地 return value == condition; @Geoffroy 是的,你是对的,我就是这样做的,所以我可以添加 exit if this happens 行来澄清我的目标。 它也会寻找其他元素,是否只有一个独特的元素要删除? 您需要像 remove_first 这样的功能,但没有这样的内置功能。为什么不自己写一篇?这很简单。 @P0W adjacent_find 后跟 erase_after 可以解决问题,请参阅我的回答 【参考方案1】:

如果只想删除第一个匹配项,可以使用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, ","));

Live Example

一旦找到匹配项,这将停止。请注意,adjacent_find 采用二元谓词,通过仅比较第二个参数,我们在要删除的元素之前得到一个迭代器,因此 erase_after 可以实际删除它。复杂性是O(N),所以你不会比这更有效。

【讨论】:

很好地使用了adjacent_find(我什至不知道这个功能)。投赞成票:-) @Angew 我以前用过它,但几天前我也在 std-proposals 论坛上读到过它,它被用来表明一个序列随着&lt; 严格增加(而不是&lt;=,正如std::is_sorted 将显示的那样)。所以它在工作记忆中有点“新鲜”。 等一下,当你实际上是在 hacking adjacent_find 在这里做你想做的事时,你正在四处投票其他答案?那是新的...... @Angew 啊,好点子。它没有,但现在它使用mylist.before_begin() @Nim 顺便说一句,我不认为这是对adjacent_find 的黑客攻击。前向迭代器天生就是由adjacent_find 处理的,因为你真的需要一直向前看,因为你不能在O(1) 中向后看。【参考方案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;

【讨论】:

好的解决方案+1。我会写 it=list.begin() 作为初始化更明确一些,oit=it,++it; 在循环结束时,但这显然与你写的一样。【参考方案3】:

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

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;
    
  

一个更完整的例子...

【讨论】:

@TemplateRex,这是针对特殊容器的专用算法,非常欢迎OP将容器类型更改为特定的。虽然这里 begin 不是标准的开始,因此接受一个简单的范围会让人感到困惑。此外,不要忘记erase_after!【参考方案4】:

标准库中没有可以直接应用的东西。其实是有的。请参阅@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;

【讨论】:

@TemplateRex 这是一个特殊容器的特殊算法,真的。它只是erase_after 与搜索相结合的便利包装。我已经反映了您使用纯std 解决方案的答案,但如果您发现自己一遍又一遍地使用erase_before(find_before_first()),您不妨将这个组合包装在一个函数中。 编写一个函数是可以的,但当需要一对前向迭代器时,不能使用硬编码的容器参数。 find_before_first 是需要包装的基本原语,而这并不是真正特定于 forward_list 的。 @TemplateRex 如果您还想包装erase_after() 调用,您必须 传递容器。是的,这个函数可以在内部调用你的find_before_first()(并且更短),但它仍然可以有一个存在的正当理由。 好的,如果您可以进行令牌编辑,我将删除赞成票。尽管如此,erase_after / find_first_before 应该足够短,以至于我不会包装它。但是我可以看到您可能想要的观点,尽管我宁愿在存在erase_after 成员而不是将forward_list 硬编码为参数时使用它;-) @TemplateRex 完成,谢谢。现在 OP 有 3 种方法可供选择:您的 std 库使用、Nim 的通用容器和我的 forward_list。我认为这是一个不错的结果:-)【参考方案5】:

当我在 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 = &amp;(*p_addr)-&gt;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&lt;T&gt;,我们不能访问链接节点的指针,而是让那些尴尬的“迭代器在实际操作之前指向一个节点”。我们的解决方案变成了

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 类型的指针,等于&amp;*std::next(it),这足以我们使用它,但实际上要确保它成为空指针有点麻烦当std::next(it)==NIL,作为标准不会让我们采取&amp;*NIL)。

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

【讨论】:

以上是关于我如何有效地从 forward_list 中删除_if 只有一个元素?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不使用 Set 的情况下有效地从数组中删除重复项

如何有效地从 ArrayList 或字符串数​​组中删除所有空元素?

如何有效地从另一个字符串中找到的字符串中删除重复字符?

有效地从 32 位数据类型中删除 2 位

有效地从 lua 表中删除 nil 值

如何在 pySpark 中有效地从字符串数据框中替换多个正则表达式模式的所有实例?