Search a vector of objects by object attribute
Asked Answered
W

4

59

I'm trying to figure out a nice way to find the index of a certain object in a vector - by comparing a string to a member field in the object.

Like this:

find(vector.begin(), vector.end(), [object where obj.getName() == myString])

I have searched without success - maybe I don't fully understand what to look for.

Wapiti answered 20/3, 2013 at 7:49 Comment(2)
Can there be more than one object with the same name? Would you want to find all of them?Nasia
How would one search within two containers of objects for matching member variables?Vaginismus
D
80

You can use std::find_if with a suitable functor. In this example, a C++11 lambda is used:

std::vector<Type> v = ....;
std::string myString = ....;
auto it = find_if(v.begin(), v.end(), [&myString](const Type& obj) {return obj.getName() == myString;})

if (it != v.end())
{
  // found element. it is an iterator to the first matching element.
  // if you really need the index, you can also get it:
  auto index = std::distance(v.begin(), it);
}

If you have no C++11 lambda support, a functor would work:

struct MatchString
{
 MatchString(const std::string& s) : s_(s) {}
 bool operator()(const Type& obj) const
 {
   return obj.getName() == s_;
 }
 private:
   const std::string& s_;
};

Here, MatchString is a type whose instances are callable with a single Type object, and return a boolean. For example,

Type t("Foo"); // assume this means t.getName() is "Foo"
MatchString m("Foo");
bool b = m(t); // b is true

then you can pass an instance to std::find

std::vector<Type>::iterator it = find_if(v.begin(), v.end(), MatchString(myString));
Divorce answered 20/3, 2013 at 7:51 Comment(2)
You need to use find_if if you're using a functor.Shaffert
How would one search within two containers of objects for matching member variables?Vaginismus
A
7

In addition to the Lambda and the handwritten functor used by juancho, you have the possibility to use boost::bind (C++03) or std::bind (C++11) and a simple function:

bool isNameOfObj(const std::string& s, const Type& obj)
{ return obj.getName() == s; }

//...
std::vector<Type>::iterator it = find_if(v.begin(), v.end(), 
  boost::bind(&isNameOfObj, myString, boost::placeholders::_1));

Or, if Type has a method isName:

std::vector<Type>::iterator it = find_if(v.begin(), v.end(), 
  boost::bind(&Type::isName, boost::placeholders::_1, myString));

This is just for completeness. In C++11 I'd prefer Lambdas, in C++03 I'd use bind only if the comparison function itself exists already. If not, prefer the functor.

PS: Since C++11 has no polymorphic/templated lambdas, bind still has it's place in C++11, e.g. if the parameter types are unknown, hard to spell, or otherwise not easy to deduce.

Aalesund answered 20/3, 2013 at 8:23 Comment(0)
M
4

A simple iterator may help.

typedef std::vector<MyDataType> MyDataTypeList;
// MyDataType findIt should have been defined and assigned 
MyDataTypeList m_MyObjects;
//By this time, the push_back() calls should have happened
MyDataTypeList::iterator itr = m_MyObjects.begin();
while (itr != m_MyObjects.end())
{
  if(m_MyObjects[*itr] == findIt) // any other comparator you may want to use
    // do what ever you like
}
Motet answered 20/3, 2013 at 11:0 Comment(1)
shouldn't it have ++itr in the while loop?Unstrained
H
1

With C++20, it becomes even easier using std::ranges::find with projection:

struct MyStruct {
    std::string x;
    std::string y;
};

std::vector<MyStruct> v = {{"Hello", "World"}, {"asdf", "zxcv"}};
auto iter = std::ranges::find(v, "Hello", &MyStruct::x);

See it online

Hasdrubal answered 20/11, 2023 at 23:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.