I was thinking about using remove_if on a vector of strings as follows in the pseudo code below:
for(some strings in a given set) {
remove_if(myvec.begin(), myvec.end(), string_matches_current_string);
}
Now, I am aware I can define the predicate and make this work easily. But I was wondering if there is a standard template function that I could use in place of the predicate above to make this work. I was looking around and couldn't find one. Appreciate any ideas with examples. Thanks!
Why using std::remove_if if you already know the value you want to remove? Use std::remove, which removes the items in the provided range, that match the given value:
std::vector<std::string>::iterator new_end = my_vec.end();
for(const auto ¤t_set_string : some_set)
new_end = std::remove(myvec.begin(), new_end, current_set_string);
my_vec.erase(new_end, my_vec.end()); // effectively remove them from the vector.
Note that I used the range-based for loop just to make this shorter, but you should use a regular loop if you can't use C++11.
I'm pretty sure there isn't a standard function but you can easily write the whole expression using a C++11 lambda:
std::remove_if(myvec.begin(), myvec.end(),
[&compare_me](std::string const& cmp) -> bool
{
return compare_me == cmp;
});
With compare_me being the "current" string set by the outer loop.
Keep in mind that remove_if returns an iterator to one past the last valid element so in order to get the correct myvec, you have to erase the elements between the iterator returned by remove_if and myvec.end().
For a non-C++11 implementation you'd have to turn the lambda into a function or functor. If you turn it into a functor you can pass the functor directly, if you turn it into a function you'll have to use something like boost::bind to provide the necessary glue.
Related
I'm trying to use a c++20 constrained algorithm for the erase-remove idiom:
std::vector<int> v;
v.erase(std::unique(std::begin(v), std::end(v)), std::end(v));
but when I do a simple transformation:
v.erase(std::ranges::unique(v), std::end(v));
I get an error that the arguments to erase don't match:
error: no matching function for call to 'std::vector<int>::erase(std::ranges::borrowed_subrange_t<std::vector<int>&>, std::vector<int>::iterator)'
A similar error is produced if the second argument is std::ranges::end(v).
How can I get this to work?
The question originally used remove instead of unique, but there is an overloaded std::erase for all containers that makes that particular use case less motivating.
std::ranges::unique (and std::ranges::remove) returns a sub range from the first removed element to the end of the container so you need to use std::begin before passing to std::vector::erase:
v.erase(std::ranges::begin(std::ranges::remove(v, 42)), std::end(v));
v.erase(std::ranges::begin(std::ranges::unique(v)), std::end(v));
Another option would be decomposing the subrange returned by std::ranges::remove/unique, and use those iterators:
auto [Beg, End] = std::ranges::remove(v, 42);
v.erase(Beg, End);
It doesn't work since std::ranges::remove() returns not iterator but range. But even if you try v.erase(std::ranges::remove(...)) it will not work, because vector does not have erase() overload which takes range as parameter.
Instead, take a look at std::erase() (defined in <vector>). What you need is probably just std::erase(v, 42).
Full disclosure, this may be a hammer and nail situation trying to use STL algorithms when none are needed. I have seen a reappearing pattern in some C++14 code I am working with. We have a container that we iterate through, and if the current element matches some condition, then we copy one of the elements fields to another container.
The pattern is something like:
for (auto it = std::begin(foo); it!=std::end(foo); ++it){
auto x = it->Some_member;
// Note, the check usually uses the field would add to the new container.
if(f(x) && g(x)){
bar.emplace_back(x);
}
}
The idea is almost an accumulate where the function being applied does not always return a value. I can only think of a solutions that either
Require a function for accessing the member your want to accumulate and another function for checking the condition. i.e How to combine std::copy_if and std::transform?
Are worse then the thing I want to replace.
Is this even a good idea?
A quite general solution to your issue would be the following (working example):
#include <iostream>
#include <vector>
using namespace std;
template<typename It, typename MemberType, typename Cond, typename Do>
void process_filtered(It begin, It end, MemberType iterator_traits<It>::value_type::*ptr, Cond condition, Do process)
{
for(It it = begin; it != end; ++it)
{
if(condition((*it).*ptr))
{
process((*it).*ptr);
}
}
}
struct Data
{
int x;
int y;
};
int main()
{
// thanks to iterator_traits, vector could also be an array;
// kudos to #Yakk-AdamNevraumont
vector<Data> lines{{1,2},{4,3},{5,6}};
// filter even numbers from Data::x and output them
process_filtered(std::begin(lines), std::end(lines), &Data::x, [](int n){return n % 2 == 0;}, [](int n){cout << n;});
// output is 4, the only x value that is even
return 0;
}
It does not use STL, that is right, but you merely pass an iterator pair, the member to lookup and two lambdas/functions to it that will first filter and second use the filtered output, respectively.
I like your general solutions but here you do not need to have a lambda that extracts the corresponding attribute.
Clearly, the code can be refined to work with const_iterator but for a general idea, I think, it should be helpful. You could also extend it to have a member function that returns a member attribute instead of a direct member attribute pointer, if you'd like to use this method for encapsulated classes.
Sure. There are a bunch of approaches.
Find a library with transform_if, like boost.
Find a library with transform_range, which takes a transformation and range or container and returns a range with the value transformed. Compose this with copy_if.
Find a library with filter_range like the above. Now, use std::transform with your filtered range.
Find one with both, and compose filtering and transforming in the appropriate order. Now your problem is just copying (std::copy or whatever).
Write your own back-inserter wrapper that transforms while inserting. Use that with std::copy_if.
Write your own range adapters, like 2 3 and/or 4.
Write transform_if.
I have the following object
std::vector<std::vector<std::string>> vectorList;
Then I add to this using
std::vector<std::string> vec_tmp;
vec_tmp.push_back(strDRG);
vec_tmp.push_back(strLab);
if (std::find(vectorList.begin(), vectorList.end(), vec_tmp) == vectorList.end())
vectorList.push_back(vec_tmp);
The std::vector<std::string>s contained vectorList are only ever 2-dimensional and there are no duplicates. This works great, but I now only want to check if vectorList contains an item that index zero equal to the current strDrg. In C# I would not even be thinking about this, but this does not seem straight forward using C++. How can I find if a vector exists in vectorList where strDrg already exists in vectorList.at(i)[0]?
Note: I can use boost.
Use find_if with a lambda:
std::find_if(vectorList.begin(), vectorList.end(),
[&strDrg](const std::vector<std::string>& v) { return v[0] == strDrg; });
It seems you don't need the full power of vector for you inner elements. Consider using:
std::vector<std::array<std::string, 2>>
instead.
For doing exactly what you asked, std::find_if with a lambda as #chris proposed in comments is the best:
std::find_if(ob.begin(), ob.end(),
[&](const auto x){return x[0] == strDRG;});
// Replace auto with "decltype(ob[0])&" until
//you have a C++1y compiler. Might need some years.
But if you only ever have exactly two elements, consider using a std::array<...>, a std::pair<...> or a std::tuple<...> instead of the inner vector.
For tuple and pair, you need to access the first element differently:
pair : member first
tuple: use get<0>(x);
The standard way to remove certain elements from a vector in C++ is the remove/erase idiom. However, the predicate passed to remove_if only takes the vector element under consideration as an argument. Is there a good STL way to do this if the predicate is conditional on other elements of the array?
To give a concrete example, consider removing all duplicates of a number immediately following it. Here the condition for removing the n-th element is conditional on the (n-1)-th element.
Before: 11234555111333
After: 1234513
There's a standard algorithm for this. std::unique will remove the elements that are duplicates of those preceding them (actually, just like remove_if it reorganizes the container so that the elements to be removed are gathered at the end of it).
Example on a std::string for simplicity:
#include <string>
#include <iostream>
#include <algorithm>
int main()
{
std::string str = "11234555111333";
str.erase(std::unique(str.begin(), str.end()), str.end());
std::cout << str; // 1234513
}
Others mentioned std::unique already, for your specific example. Boost.Range has the adjacent_filtered adaptor, which passes both the current and the next element in the range to your predicate and is, thanks to the predicate, applicable to a larger range of problems. Boost.Range however also has the uniqued adaptor.
Another possibility would be to simply keep a reference to the range, which is easy to do with a lambda in C++11:
std::vector<T> v;
v.erase(std::remove_if(v.begin(), v.end(),
[&](T const& x){
// use v, with std::find for example
}), v.end());
In my opinion, there will be easier to use simple traversal algorithm(via for) rather then use std::bind. Of course, with std::bind you can use other functions and predicates(which depends on previous elements). But in your example, you can do it via simple std::unique.
Perhaps this is a duplicate but I did not find anything searching:
When erase(value) is called on std::multiset all elements with the value found are deleted. The only solution I could think of is:
std::multiset<int>::iterator hit(mySet.find(5));
if (hit!= mySet.end()) mySet.erase(hit);
This is ok but I thought there might be better. Any Ideas ?
auto itr = my_multiset.find(value);
if(itr!=my_multiset.end()){
my_multiset.erase(itr);
}
I would imagine there is a cleaner way of accomplishing the same. But this gets the job done.
Try this one:
multiset<int> s;
s.erase(s.lower_bound(value));
As long as you can ensure that the value exists in the set. That works.
if(my_multiset.find(key)!=my_multiset.end())
my_multiset.erase(my_multiset.equal_range(key).first);
This is the best way i can think of to remove a single instance in a multiset in c++
This worked for me:
multi_set.erase(multi_set.find(val));
if val exists in the multi-set.
I would try the following.
First call equal_range() to find the range of elements that equal to the key.
If the returned range is non-empty, then erase() a range of elements (i.e. the erase() which takes two iterators) where:
the first argument is the iterator to the 2nd element in the returned
range (i.e. one past .first returned) and
the second argument as the returned range pair iterator's .second one.
Edit after reading templatetypedef's (Thanks!) comment:
If one (as opposed to all) duplicate is supposed to be removed: If the pair returned by equal_range() has at least two elements, then erase() the first element by passing the the .first of the returned pair to single iterator version of the erase():
Pseudo-code:
pair<iterator, iterator> pit = mymultiset.equal_range( key );
if( distance( pit.first, pit.second ) >= 2 ) {
mymultiset.erase( pit.first );
}
We can do something like this:
multiset<int>::iterator it, it1;
it = myset.find(value);
it1 = it;
it1++;
myset.erase (it, it1);
Here is a more elegant solution using "if statement with initializer" introduced in C++17:
if(auto it = mySet.find(value); it != mySet.end())
mySet.erase(value);
The advantage of this syntax is that the scope of the iterator it is reduced to this if statement.
Since C++17 (see here):
mySet.extract(val);
auto itr=ms.find(value);
while(*itr==value){
ms.erase(value);
itr=ms.find(value);
}
Try this one It will remove all the duplicates available in the multiset.
In fact, the correct answer is:
my_multiset.erase(my_multiset.find(value));