Suppose I have the following class
class Human
{
public:
Human();
Human(string,int);
virtual ~Human();
string getName();
protected:
private:
string name;
int staj;
};
I have created list with 2 elements that I pushed in
list<Human> mylist;
Human x ("Mike",13);
Human y("pavlek",33);
I am trying to remove if there is element with name "Mike",I tried removing it like this :
for(list<Human>::iterator it=mylist.begin();it!=mylist.end();++it)
{
if(it->getName()=="Mike")
{
mylist.remove(it);
cout<< "removed";
cout<<it->getName();
}
}
However I get error at passing the value to the remove() function,what should I exactly pass in order to delete this element from the list?
You have simply mistaken erase and remove. According to the C++ reference, remove is used to remove from the list all elements whose values are equals to the given parameter. On the other hand, erase removes a single element given its position or a range of elements given the start and end positions.
If you only need to delete the first element containing "Mike" as its name, simply do something like this:
for(list<Human>::iterator it=mylist.begin();it!=mylist.end();++it)
{
if(it->getName() == "Mike")
{
mylist.erase(it);
break;
}
}
Please notice that after using erase, your iterator will be invalidated. You can circumvent it by using the returned value of erase, which is the next valid iterator value. This detail is important if your list might contain multiple elements whose name is "Mike".
Matheus Portela's solution was the old C++98 method. It's a lot easier now:
mylist.remove_if( [](Human const& h){return h.getName()=="Mike";} );
The condition here is [](Human const& h){return h.getName()=="Mike";}. That is a lambda expression which returns true if the Human h should be removed. You can test any other property or combination of properties there. The { } part of the lambda is a real function body; you could even have for-loops in there:
Other examples:
mylist.remove_if( [](Human const& h){return h.getName().size() > 4; } );
mylist.remove_if( [](Human const& h) {
for (char c: h.getName())
if (c=='i') return true; // remove if name contains an i
return false; } );
Mind you, the latter would be easier with std::any_of.
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.
[UPDATE: My problem is solved! Lots of thanks to Mike Seymour and Niall and all you guys!]
My code has errors in the for loop and I do not know how to fix it :(
MyClass::ITECH7603Class(set<Student>* students) {
/* Initialize dynamically the group field */
group = new map<string, Student>();
for (set<Student>::iterator it = students->begin(); it != students->end(); it++) {
addStudent(it);
}
}
void MyClass::addStudent(Student* studentPtr) {
string fullName = studentPtr->getName() + " " + studentPtr->getSurname();
group->insert(pair<string, Student>(fullName, *studentPtr));
}
So the main idea is to loop through all students in the set, and add each student into a map group. Any help? Thank you very much!
for (set<Student>::iterator it = students->begin; it != students->end; it++) {
addStudent(it);
}
should be:
for (set<Student>::iterator it = students->begin(); it != students->end(); it++) {
//^^ //^^
addStudent(it);
}
addStudent takes a pointer, while it is an iterator, so can't be passed directly.
You should change addStudent to take either a value or a pointer/reference to const:
// option 1
void addStudent(Student);
addStudent(*it);
// option 2
void addStudent(Student const &);
addStudent(*it);
// option 3
void addStudent(Student const *);
addStudent(&*it);
If, as you say in a comment, you must leave it taking a mutable pointer, then you'll need some grotesquery to deal with the fact that elements of the set are immutable:
// nasty option
addStudent(const_cast<Student*>(&*it));
// slightly less nasty option
Student copy = *it;
addStudent(©);
Beware that the first option will give undefined behaviour if the function uses the dodgy pointer to make any modification to the Student object stored in the set. The second makes a temporary copy, which can be modified without breaking the set. This is fine as long as addStudent only stores a copy of the object passed to it, not the pointer itself, which will become invalid when copy is destroyed.
In c++11 you can use range for sytax:
for (const auto &student : *students)
{
addStudent(it);
}
Then change addStudent function signature to accept reference:
void MyClass::addStudent(const Student &student) {
While you've gotten answers that "fix" your code to the extent of compiling and producing results that you apparently find acceptable, I don't find them very satisfying in terms of code style. I would do this job rather differently. In particular, my code to do this wouldn't have a single (explicit) loop. If I needed to do approximately what you're asking for, I'd probably use code something like this:
std::pair<std::string, Student> make_mappable(Student &stud) {
return std::make_pair(stud.getName() + " " + stud.getSurName(), stud);
}
std::map<std::string, Student> gen_map(std::set<Student> const &input) {
std::map<std::string, Student> ret;
std::transform(input.begin(), input.end(),
std::inserter(ret, ret.end()),
make_mappable);
return ret;
}
There definitely would not be any new in sight, nor would there be any passing a pointer to a Student.
OTOH, since the data you're using as the key for your map is data that's already in the items in the set, it may more convenient all around to continue to use a set, and just specify a comparison function based on the student's name:
struct by_given_name {
bool operator()(Student const &a, Student const &b) const {
if (a.getName() < b.getName())
return true;
if (b.getName() < a.getName())
return false;
return a.getSurName() < b.getSurName();
}
};
std::set<Student, by_given_name> xform(std::set<Student> const &in) {
return std::set<Student, by_given_name>{in.begin(), in.end()};
}
For what its worth, a Live Demo of the latter.
Whether the latter is practical will typically depend on one other factor though: your ability to create a Student from only a name/surname. If you can't do that, searching by name will be inconvenient (at best), so you'd want to use a map.
I realize this probably isn't much (if any) help in completely what's apparently home-work for a class--but even if your class prevents you from actually turning in decent code, it seems worthwhile to me to at least try to learn to write decent code in addition to what it requires. If you do pass the class and get a job writing code, you'd probably rather your coworkers didn't want to hurt you.
I want to have in a google protocol buffer repeated field only unique elements. In other words, need to use it as a std::set instead of std::vector.
Any idea which is the simplest and most efficient way to do that?
EDIT: I wouldn't want to use any iterators to loop through all the elements if possible.
Ok, as the comments from the question stated, there isn't any way of doing this without using iterators.
However, maybe someone else is interested in this, here is the function i coded to achieve this. This will take as parameters a RepeatedPtrField< T >*(the list) and a std::string(key of the new object that we intend to add to the list) and will return the element that matches the id, or NULL if there isn't any entry with this key in the RepeatedField list.
This way, you can easy keep a list of unique elements directly in a RepeatedField without using any other std structure:
template <class T>
T* repeatedFieldLookup( google::protobuf::RepeatedPtrField< T >* repeatedPtrField, std::string id)
{
google::protobuf::internal::RepeatedPtrOverPtrsIterator<T> it = repeatedPtrField->pointer_begin();
for ( ; it != repeatedPtrField->pointer_end() ; ++it )
{
CommonFields * commonMessage = (CommonFields*) (*it)->GetReflection()->
MutableMessage ((*it), (*it)->GetDescriptor()->FindFieldByName ("common"));
if(commonMessage->id() == id)
{
return *it;
}
}
return NULL;
}
NOTE: in the example above, the proto message will ALWAYS have a field called common(which in my case is also a proto message). You can replace that by anything that you want to make the comparison from your proto messages.
In the case where I had this class:
class Description : public ::google::protobuf::Message {
// ...
inline void add_field(const ::std::string& value);
inline const ::google::protobuf::RepeatedPtrField< ::std::string>& field() const;
// ...
};
I used std::find to only add a value if it didn't exist in the list:
#include <algorithm>
void addField(Description& description, const std::string& value) {
const auto& fields = description.field();
if (std::find(fields.begin(), fields.end(), value) == fields.end()) {
description.add_field(value);
}
}
hello i got an iterator running on a multimap ,my multimap includes two fields the first 1 is the key value which is a string Lastname and the second 1 is the data which is a pointer to an object called Employee.
in my program i need to be able to find an element inside my map using iterator (cause i am trying to find the data by its ID and not by the key value).
i want to have some sort of indicator that will tell me if the element i am looking for is inside my map or not.
thanks in advance.
and this is my Find code :
MultiMap::iterator iterator;
iterator=m_municipalityMap.begin();
bool flag = false;
while(!(flag) && iterator != m_municipalityMap.end())
{
if(strcmp(iterator->second->GetId() , id) == 0)
{
flag=true;
break;
}
iterator++;
}
if (flag==false)
cout << "could not find Employee"<<endl;
return iterator;
what i need to know if there is a way to know if the iterator stands on nothing like comparing to NULL on pointers
You could use the std::find_if algorithm with a suitable predicate that will handle finding
your Employee based on the ID.
This is an adaption of an example found in "The C++ Standard Library - A Tutorial and Reference" by Nicolai M. Josuttis, Addison-Wesley, 1999:
class value_id_equals
{
private:
string m_id;
public:
// constructor (initialize id to compare with)
value_id_equals (string id)
: m_id(id) { }
// comparison
bool operator() (pair<string, Employee*> elem)
{
return (!strcmp(elem.second->GetID(), m_id.c_str()));
}
};
...
pos = find_if(your_map.begin(),your_map.end(), // linear complexity
value_id_equals(theID));
If you need two keys in the container you could try Boost Multi-index Container.
Another solution is to create two maps, each one with own key, and keep your data in each by (smart) pointers.