I'm writing a program for an assignment - it's supposed to be a database
for information about employees in a company. Basically, a vector containing
structures (individual employees).
The trouble I'm having is that remove_if erases everything from the vector - instead of an individual employee.
If I understood documentation/other topics correctly, that function should
do two things - rearrange elements of the vector, and return an
iterator to the first element outside the new range - but it doesn't do
it, it returns an iterator to the first element - and so when the
erase() function is called, all elements are deleted. At least
that's what I found when debugging it.
Here's a mcve of my code:
#include <iostream>
#include <vector>
#include <algorithm>
struct employee {
int number;
};
int main()
{
//creating the vector and adding some values to it
employee one{ 1 };
employee two{ 2 };
employee three{ 3 };
std::vector <employee> staff{ one, two, three };
int m = 2; //some parameter I want to pass to lambda function
auto it = std::remove_if(staff.begin(), staff.end(),
[m](employee a) {
if (a.number == 2)
return true; }
);
staff.erase(it, staff.end());
for (auto it = staff.begin(); it != staff.end(); it++)
std::cout << it->number << std::endl;
system("pause");
return 0;
}
I realise that I could've done the same thing in a loop - in fact, I did, but I just can't wrap my head around why doesn't this approach work. Also, a list would've probably been a better choice for this program (with it, the for loop would have taken fewer instructions to compute), but I've already finished the program, and right now I just really want to know why didn't remove_if work.
Thanks!
EDIT: As #drescherjm pointed out, that was due to the fact that the lambda function didn't return false when the when the if statement wasn't met.
So the question is answered.
The main problem is you are not returning a value when your condition in your lambda is not met. This is undefined behavior not to return a value.
auto it = std::remove_if(staff.begin(), staff.end(),
[m](employee a) {
if (a.number == 2)
return true; }
);
A simple solution is to remove the if and just return the conditional.
auto it = std::remove_if(staff.begin(), staff.end(),
[m](employee a) {
return (a.number == 2);
}
);
However as #killzonekid mentioned this is not correct because you are still not using the parameter.
auto it = std::remove_if(staff.begin(), staff.end(),
[m](employee a) {
return (a.number == m);
}
);
Replacing the fixed 2 with m should take care of that.
Related
I'm using C++20.
I have an object MyObject that contains a variable std::set<std::set<int>> nestedSet. I need to iterate through nestedSet and remove from the second-level sets an element that matches a search criteria. So far, I've tried the implementations:
void MyObject::removeFromNestedSet(int criteria) {
for(auto s : nestedSet){
std::erase_if(s, [&criteria](int i){return i == criteria;});
}
}
and
void MyObject::removeFromNestedSet(int criteria) {
for(auto s : nestedSet){
auto it = s.find(criteria);
if(it != s.end()){
s.erase(it, s.end());
}
}
}
Viewing the code progression with a debugger, I can see that within the frame of the removeFromNestedSet function, the element in the set matching the criteria IS removed. However, this removal is not reflected when observing the this->nestedSet object.
I haven't worked with C++ in a few years, but I suspect this is an issue with needing the range-based loop to point to the actual nested sets within nestedSet rather than a copy of the nested set?
You are having difficulty because a std::set's elements are always const.
This is because a std::set's elements are always ordered by their values. Changing a value could violate the order.
You must remove each element from your outer std::set before you can modify it.
void MyObject::removeFromNestedSet(int criteria) {
std::set<std::set<int>> newNestedSet;
while ( ! nestedSet.empty() ) {
// Remove an inner set, so it can be modified
auto setNode = nestedSet.extract( nestedSet.begin() );
// Modify the set
std::erase_if(setNode.value(), [&](int i){return i == criteria;});
// Place the result in a new set
newNestedSet.insert(std::move(setNode));
}
nestedSet = std::move(newNestedSet);
}
This solution doesn't make any copies of your data and preserves the integrity of any pointers or references to your stored ints.
Note that your sets may be in a different order after your modification.
Several options.
If you insist on that particular data-structure, you'll have to copy the entire thing over to a new set and then swap/move that with/into the member.
std::set<std::set<int>> dest;
for (auto const& n: nestedSet) {
std::set<int> ndest;
for (int i: n) {
if (i != criteria) {
ndest.insert(i);
}
}
dest.emplace(std::move(ndest));
}
nestedSet = std::move(dest);
Needless to say, this is terrible.
But maybe you don't really need to be dealing with a set to begin with. Often a vector is the better choice: std::vector<std::set<int>> nestedSet; (maybe even the inner set can be a vector!).
Then you do the algorithm like you tried:
for(auto& s : nestedSet) {
// ^--------- important, or you would be modifying a copy
std::erase_if(s, [&criteria](int i){return i == criteria;});
}
If you absolutely need a set, you could have an std::set<std::unique_ptr<std::set<int>>> nestedSet
for(auto const& p : nestedSet) {
// p is an std::unique_ptr<std::set<int>> const&
std::erase_if(*p, [&criteria](int i){return i == criteria;});
}
Of course this does introduce yet another layer of indirection, but with two nested sets this almost doesn't matter anymore.
I have two classes, each has a vector of pointers to Data. What I want to do is to assign pointers in the vector of the class Sample2 to pointers in the vector of the class Sample1.
The problem is that as I assign pointers in the second vector, the order in witch they are stored is that of the first vector. I would like to store them in the order of insertion.
Here is a minimal reproducible example of the code:
#include <iostream>
#include <vector>
using namespace std; //for sample purposes
// For simplicity, the data is just a string in this example.
using Data = string;
// In the real code there is a class with a certain vector as a member,
// but for this example we can reduce it to just the vector.
using Sample1 = vector<Data*>;
Class Sample2 — the problem is here
class Sample2 {
vector<Data*> autodromos2;
public:
vector<Data*>& getAutodromos() { return autodromos2; }
// ** This is the function with the problem. **
void addAutodromos2(vector<string>& arguments, vector<Data*>& autodromos)
{
for (Data* a : autodromos) {
for (string &s : arguments) {
if (s == *a) { // The real test is more complex.
getAutodromos().push_back(a);
break;
}
}
}
}
};
Main function (generate data and call addAutodromos2)
int main()
{
// Create the list of elements to add to a `Sample2`.
// Note that these are strings, not Data objects (in the real code).
vector<string> arguments { "fourth", "first", "third" };
// Create the `Sample1` data with which to work.
Sample1 s1 {
new Data("first"), new Data("second"), new Data("third"),
new Data("fourth"), new Data("fifth")
};
// Create the `Sample2` data from the list and `s1`.
Sample2 s2;
s2.addAutodromos2(arguments, s1);
// Diagnostic:
for (Data* a : s2.getAutodromos()) {
cout << *a << endl;
}
}
The output is
first
third
fourth
when it should be
fourth
first
third
Actually the sequence problem with loops in addAutodromos2() you need to change function with below code:
for (string s : arguments)
{
for (Data* a : autodromos)
{
if (s == *a) { // The real test is more complex.
getAutodromos().push_back(a);
break;
}
}
}
Switch the for-loops. output is fourth first third
Hope this will help.
There is a school of thought that says if you have nested loops inside a function, you probably are not thinking abstractly enough. While that might be an overstatement at times, it does have value in this situation. Let's look at the inner loop.
for (string s : arguments) {
if (s == *a) {
getAutodromos().push_back(a);
break;
}
}
This loop searches for *a in arguments and if found does something. The search is a concept that could be abstracted away into its own function, let's call it found, a function that returns a bool.
// Preliminary revision
void addAutodromos2(vector<string>& arguments, vector<Data*>& autodromos)
{
for (Data* a : autodromos) {
if ( found(arguments, *a) ) {
getAutodromos().push_back(a);
}
}
}
With only one loop to look at, it should be clearer what the problem is. Elements are added to getAutodromos() in the order they appear in autodromos. To use the order within arguments, you need to loop through it. (I'll change the name of the helper function to find_by_name and have it return either an iterator to the found element or the end iterator. A boolean return value is no longer adequate.)
// Final revision
void addAutodromos2(vector<string>& arguments, vector<Data*>& autodromos)
{
for (string s : arguments) {
auto result = find_by_name(autodromos, s);
if ( result != autodromos.end() ) {
getAutodromos().push_back(*result);
}
}
}
A missing piece here is the find_by_name function. The good news is that this task is so common, that functionality is part of the standard library, in the header <algorithm>. The bad news is that there is a bit of typing to use the library function, as the arguments are more complex (for greater flexibility). You may want to define a wrapper to specialize it to your case.
// Returns an iterator to the element with the indicated name, or
// autodromos.end() if not found.
static auto find_by_name(const vector<Data*> & autodromos, const string & name)
{
return std::find_if(autodromos.begin(), autodromos.end(), [&name](Data *a){
return name == *a; // or name == a->get_name(), when Data is more complex
});
}
Note that if the real test was as simple as comparing name == *a, then std::find could be used instead of std::find_if, and there would be no need to use a lambda.
Don't forget to #include <algorithm> earlier in the file.
I'm trying to create a vector that contains various class times. Afterwards, I would compare these times to see which one is earlier through a sorting function.
Edit: after some people mentioned, I do wish to do this with an older version of C++ (prior to 11) because it's what my instructor requested
Would there be a way to do this with push_back?
So far, I have this in my main file:
std::vector<Time> times (Time t1(4,5,4), Time t2(3,5,4));
std::sort(times.begin(), times.end(), IsEarlierThan);
and this in my Time.cpp file:
#include <iostream>
#include "Time.h"
Time::Time() {
hour = 0;
minute = 0;
second = 0;
}
Time::Time(int theHour, int theMinute, int theSecond) {
hour = theHour;
minute = theMinute;
second = theSecond;
}
int Time::getHour() const {
return hour;
}
int Time::getMinute() const {
return minute;
}
int Time::getSecond() const {
return second;
}
bool IsEarlierThan(const Time& t1, const Time& t2){
if (t1.getHour() < t2.getHour()) return true;
else if (t1.getHour() == t2.getHour()){
if (t1.getMinute() < t2.getMinute()) return true;
else if (t1.getMinute() == t2.getMinute()){
if(t1.getSecond() < t2.getSecond()) return true;
}
}
return false;
}
The vector declaration is not correct, so my question would be how would I add these times (including hour, minute, and second) as separate vector values and compare them to each other (eg is 17:23:56 earlier than 19:49:50).
The IsEarlierThan function works, though I am unsure of how to implement it with a vector.
Thanks for any help!
Vector declaration is correct, vector construction is incorrect.
std::vector does not have a constructor which accepts two arguments of vector's element type.
If you want to initialize vector with the values from your code - change this line to:
std::vector<Time> times {Time(4,5,4), Time(3,5,4)};
See list initialization for detailed explanation how it works under the hood.
Edit:
For earlier than C++11 stardard - see this post.
Or if you don't care about this explicitly to be a single-statement assingment - just use push_back:
std::vector<Time> times; // create an empty vector
times.push_back(Time(4,5,4)); // append element to vector
times.push_back(Time(3,5,3));
How do I get the position of an element inside a vector, where the elements are classes. Is there a way of doing this?
Example code:
class Object
{
public:
void Destroy()
{
// run some code to get remove self from vector
}
}
In main.cpp:
std::vector<Object> objects;
objects.push_back( <some instances of Object> );
// Some more code pushing back some more stuff
int n = 20;
objects.at(n).Destroy(); // Assuming I pushed back 20 items or more
So I guess I want to be able to write a method or something which is a member of the class which will return the location of itself inside the vector... Is this possible?
EDIT:
Due to confusion, I should explain better.
void Destroy(std::vector<Object>& container){
container.erase( ?...? );
}
The problem is, how can I find the number to do the erasing...? Apparently this isn't possible... I thought it might not be...
You can use std::find to find elements in vector (providing you implement a comparison operator (==) for Object. However, 2 big concerns:
If you need to find elements in a container then you will ger much better performance with using an ordered container such as std::map or std::set (find operations in O(log(N)) vs O(N)
Object should not be the one responsible of removing itself from the container. Object shouldn't know or be concerned with where it is, as that breaks encapsulation. Instead, the owner of the container should concern itself ith such tasks.
The object can erase itself thusly:
void Destroy(std::vector<Object>& container);
{
container.erase(container.begin() + (this - &container[0]));
}
This will work as you expect, but it strikes me as exceptionally bad design. Members should not have knowledge of their containers. They should exist (from their own perspective) in an unidentifiable limbo. Creation and destruction should be left to their creator.
Objects in a vector don't automatically know where they are in the vector.
You could supply each object with that information, but much easier: remove the object from the vector. Its destructor is then run automatically.
Then the objects can be used also in other containers.
Example:
#include <algorithm>
#include <iostream>
#include <vector>
class object_t
{
private:
int id_;
public:
int id() const { return id_; }
~object_t() {}
explicit object_t( int const id ): id_( id ) {}
};
int main()
{
using namespace std;
vector<object_t> objects;
for( int i = 0; i <= 33; ++i )
{
objects.emplace_back( i );
}
int const n = 20;
objects.erase( objects.begin() + n );
for( auto const& o : objects )
{
cout << o.id() << ' ';
}
cout << endl;
}
If you need to destroy the n'th item in a vector then the easiest way is to get an iterator from the beginning using std::begin() and call std::advance() to advance how ever many places you want, so something like:
std::vector<Object> objects;
const size_t n = 20;
auto erase_iter = std::advance(std::begin(objects), n);
objects.erase(erase_iter);
If you want to find the index of an item in a vector then use std::find to get the iterator and call std::distance from the beginning.
So something like:
Object object_to_find;
std::vector<Object> objects;
auto object_iter = std::find(std::begin(objects), std::end(objects), object_to_find);
const size_t n = std::distance(std::begin(objects), object_iter);
This does mean that you need to implement an equality operator for your object. Or you could try something like:
auto object_iter = std::find(std::begin(objects), std::end(objects),
[&object_to_find](const Object& object) -> bool { return &object_to_find == &object; });
Although for this to work the object_to_find needs to be the one from the actual list as it is just comparing addresses.
I have my loop going through vector's elements. While in this loop some of the elements are being (I want them to be) removed. Although std::vector does not allow to do this, and I would like an alternative.
for(unsigned int j = 0; j < rectArray.size(); j++)
{
if( rectArray[j] == 2 )
{
rectArray.erase(rectArray.begin() + j);
}
//...
}
Do you think a std::list would be good here ? Can I use something else ?
Unless the elements of the vector are very expensive to copy, the simplest is probably to std::copy_if (or otherwise copy the ones you want to keep) into a new vector, and then swap that with the original. There's also remove_if followed by resize.
If the elements are very expensive to relocate, then a list would avoid that, but it depends what else you do with the collection. If you do something else that would be cripplingly slow with a list, then you've just moved the problem elsewhere.
I would suggest modifying your code such that it uses iterators instead of the actual vector. It's much cleaner and more efficient like this:
for (auto it = rectArray.begin(); it != rectArray.end(); ++it)
{
// Access the current element with *it
// If you want you can pass `it` and `rectArray.end()` as
// the lower and upper bounds of the new collection,
// rather than doing expensive resizes of the vector.
}
Note that auto is a C++11 feature (the way I used it). If your compiler supports that you might also want to use C++11's foreach:
for (auto it : rectArray) {
// same as before
}
Removing an element from the middle of a vector is expensive - because you have to move all the later elements down.
If you need to add/remove elements to the middle of a container then a list is generally better.
List would be better than a vector - as it will not cost you anything to remove elements from the middle of the list. Removing elements from the middle of a vector, on the other hand, has linear complexity.
Possible alternative to std::remove_if. A bit faster and doesn't require a functor, however it does not maintain order.
auto end = std::end(rectArray);
for(auto it = std::begin(rectArray); it != end; ++it)
{
if(it->remove_me()))
std::swap(*it, *--end); // or even faster *it = std::move(*--end);
}
rectArray.erase(end, std::end(rectArray));
If you are doing a lot of deletes, a list is probably the way to go. Here is some sample code to help.
#include <vector>
#include <list>
#include <algorithm>
using namespace std;
class Widget
{
public:
explicit Widget(int someNumber);
bool ShouldDelete();
bool ShouldDeleteComplex(int a, int b, int c);
private:
int _someNumber;
};
Widget::Widget(int someNumber) : _someNumber(someNumber)
{
}
bool Widget::ShouldDelete()
{
if (_someNumber > 2)
{
return true;
}
return false;
}
bool Widget::ShouldDeleteComplex(int a, int b, int c)
{
if ((a * b - c) > _someNumber)
{
return true;
}
return false;
}
int main()
{
list<Widget> lw;
lw.push_back(Widget(1));
lw.push_back(Widget(2));
lw.push_back(Widget(3));
// delete from list using functor
lw.remove_if(mem_fun_ref(&Widget::ShouldDelete));
// delete from list using lambda function
lw.remove_if([] (Widget& x) { return x.ShouldDeleteComplex(1, 2, 0); } );
vector<Widget> vw;
vw.push_back(Widget(1));
vw.push_back(Widget(2));
vw.push_back(Widget(3));
// delete using functor
vw.erase(remove_if(vw.begin(), vw.end(), mem_fun_ref(&Widget::ShouldDelete)), vw.end());
// delete using lambda function
vw.erase(
remove_if(vw.begin(), vw.end(),
[] (Widget& x) { return x.ShouldDeleteComplex(1, 2, 0); }
),
vw.end());
return 0;
}