9

Well I think the question pretty much sums it up. I have a forward_list of unique items, and want to remove a single item from it:

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

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

I mean, this method works fine but it's inefficient because it continues to search once the item is found and deleted. Is there a better way or do I need to do it manually?

quant
  • 21,507
  • 32
  • 115
  • 211

5 Answers5

12

If you only want to remove the first match, you can use std::adjacent_find followed by the member 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

This will stop as soon as a match is found. Note that the adjacent_find takes a binary predicate, and by comparing only the second argument, we get an iterator before the element we want to remove, so that erase_after can actually remove it. Complexity is O(N) so you won't get it more efficient than this.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • Nice use of `adjacent_find` (I wasn't even aware of this function). Have an upvote :-) – Angew is no longer proud of SO Oct 15 '13 at 07:54
  • @Angew I used it before, but I also just read about it a few days ago on the std-proposals forum where it was used to show that a sequence was strictly increasing with `<` (instead of `<=`, as `std::is_sorted` would show). So it was kind of "fresh" in working memory. – TemplateRex Oct 15 '13 at 07:58
  • 1
    hold on a second, you are going around down voting other answers when you are actually *hacking* `adjacent_find` to do what you want here? That's new... – Nim Oct 15 '13 at 08:07
  • @Angew ah, good point. It didn't, but now it does using `mylist.before_begin()` – TemplateRex Oct 15 '13 at 08:13
  • @Nim the Standard says for `erase_after`: "Requires: The iterator following position is dereferenceable". An empty list has `begin() == end()` and dereferencing the `end()` (or any invalid) iterator is UB. – TemplateRex Oct 15 '13 at 08:21
  • @Nim, no the `find_first_of` works fine with an empty list, and returns an iterator 1 after `before_begin()` (i.e. it returns `end()`), see http://coliru.stacked-crooked.com/a/00f87cb7f5e74a60 Note that I only derefence the 2nd element in the lambda, and `adjacent_find` guarantees not to dereference if `std::next(first)==last` – TemplateRex Oct 15 '13 at 08:27
  • 1
    @Nim btw, I don't consider this a hack of `adjacent_find`. Forward iterators are born to be processed by `adjacent_find` because you really need to do a look-ahead-by-one all the time because you can't look backwards in `O(1)`. – TemplateRex Oct 15 '13 at 08:32
  • This is an interesting solution, that will work in practice. However, technically the `mylist.before_begin()` iterator is being dereferenced to produce the first (unused) argument to the lambda expression (third argument) in the first call to `std::find_first`, and since that is a non-dereferenceable iterator, this triggers undefined behavior. – Marc van Leeuwen Jun 20 '14 at 06:06
  • @MarcvanLeeuwen I updated the answer with a small modification as discussed earlier to remove the UB. I also added a few more test cases for empty, singleton and doubleton lists, testing for first, last and missing elements. Thanks again for your input! – TemplateRex Jun 27 '14 at 12:48
3

FWIW, here's another short version

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;
}
Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • Good solution +1. I would have written `it=list.begin()` as initialisation to be somewhat more explicit, and `oit=it,++it;` at the end of the loop, but this is obviously just equivalent to what you wrote. – Marc van Leeuwen Jun 20 '14 at 06:14
2

Going to have to roll your own...

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

A more complete example...

Nim
  • 33,299
  • 2
  • 62
  • 101
  • @TemplateRex, this is a specialized algorithm for a special container, the OP is quite welcome to change the container type to be specific. Here though the *begin* is not the standard begin and would therefore be confusing to accept a simple range. Additionally, don't forget the `erase_after`! – Nim Oct 15 '13 at 08:11
  • I don't understand why you don't put nit != container.end() in the second statement of the for loop instead of a test at the beginning of the for-block. but it looks like the best answer – kingsjester Dec 02 '22 at 09:14
1

There is nothing in the standard library which would be directly applicable. Actually, there is. See @TemplateRex's answer for that.

You can also write this yourself (especially if you want to combine the search with the erasure), something like this:

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;
}
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • @TemplateRex This is a special algorithm for a special container, really. It's just a convenience wrapper for `erase_after` combined with the search. I've reflected your answer which uses a pure-`std` solution, but if you find yourself doing `erase_before(find_before_first())` over and over again, you might as well want to wrap this combo in a function. – Angew is no longer proud of SO Oct 15 '13 at 07:58
  • writing a function is OK, but not with a hardcoded container parameter when a pair of forward iterators is all that is required. It's the `find_before_first` that is the essential primitive that needs to be wrapped, and that is not really specific for `forward_list`. – TemplateRex Oct 15 '13 at 08:01
  • @TemplateRex If you want to wrap the `erase_after()` call as well, you *have to* pass the container. Yes, this function could just call your `find_before_first()` inside (and be much shorter), but it still could have a valid reason for existence. – Angew is no longer proud of SO Oct 15 '13 at 08:06
  • OK, if you can make a token edit, I'll remove the upvote. Still, the erase_after / find_first_before is short should enough that I wouldn't wrap it. But I can see your point that you might want to, although I'd rather SFINAE it on the existence of an `erase_after` member than to hardcode the `forward_list` as a parameter ;-) – TemplateRex Oct 15 '13 at 08:10
  • @TemplateRex Done, thanks. Now the OP has 3 approaches to choose from: your `std` library usage, Nim's for generic container, and mine for `forward_list`. I think that's a nice result :-) – Angew is no longer proud of SO Oct 15 '13 at 08:49
  • choices, schmoices... What happened to the One True Way? :-) – TemplateRex Oct 15 '13 at 08:54
  • @TemplateRex Ask [Area51](http://area51.stackexchange.com/). They claim sites should have more than 1 answer per question to be healthy :-) – Angew is no longer proud of SO Oct 15 '13 at 08:59
  • @TemplateRex can before_begin() be dereferenced, even though it does not use the value? – abir Apr 02 '14 at 13:20
  • @abir [No](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3797.pdf), `before_begin()` returns a "Returns: A non-dereferenceable iterator that, when incremented, is equal to the iterator returned by `begin()`." – TemplateRex Apr 02 '14 at 17:43
1

This kind of stuff used to be a standard exercise when I learned programming way back in the early '80s. It might be interesting to to recall the solution, and compare that with what one can do in C++. Actually that was in Algol 68, but I won't impose that on you and give the translation into C. Given

typedef ... T;
typedef struct node *link;
struct node { link next; T data; };

one could write, realising that one needs to pass the address of the list head pointer if is to be possible to unlink the first node:

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 */
  }
}

There are a lot of occurrences of *p_addr there; it is the last one, where it is the LHS of an assignment, that is the reason one needs the address of a pointer here in the first place. Note that in spite of the apparent complication, the statement p_addr = &(*p_addr)->next; is just replacing a pointer by the value it points to, and then adding an offset (which is 0 here).

One could introduce an auxiliary pointer value to lighten the code a bit up, as follows

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

but that is fundamentally the same code: any decent compiler should realise that the pointer value *p_addr is used multiple times in succession in the first example, and keep it in a register.

Now with std::forward_list<T>, we are not allowed access to the pointers that link the nodes, and get those awkward "iterators pointing one node before the real action" instead. Our solution becomes

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

Again we could keep a second iterator variable to hold std::next(it) without having to spell it out each time (not forgetting to refresh its value when we increment it) and arrive at essentially the answer by Daniel Frey. (We could instead try to make that variable a pointer of type *T equal to &*std::next(it) instead, which suffices for the use we make of it, but it would actually be a bit of a hassle to ensure it becomes the null pointer when std::next(it)==NIL, as the standard will not let us take &*NIL).

I cannot help feel that since the old days the solution to this problem has not become more elegant.

Marc van Leeuwen
  • 3,605
  • 23
  • 38