For example I have struct
struct A {
std::string Id;
std::string Name;
Std::string Year;
};
I defined data type look like
std::map<A, int> MyMap;
I put some item to MyMap. I want to find an item that satisfy one of below
- MyMap.find(it1); //that return iter if match Id
- MyMap.find(it2); //that return iter if match both Id and Name
- MyMap.find(it3); //that return iter if match all Id, Name,Year
I know I must define operator< in struct A but how to define that work with 3 cases above. Or Which data type instead of Map is suitable in this case.
std::map can only have one predicate for associating key with a value.
You can use different predicates with the standard algorithm std::find_if to achieve this, but it does a linear search, rather than an efficient map lookup.
If you need multiple predicates to look up an element efficiently, then you need a multi-index container. The standard library does not have such a thing, but you can implement one by using multiple maps internally, or you can use a generic solution from Boost.
If your research is still in same order, you might use something like:
struct A {
std::string Id;
std::string Name;
Std::string Year;
};
bool operator < (const A& lhs, const A& rhs) {
return std::tie(lhs.Id, lhs.Name, lhs.Year) < std::tie(rhs.Id, rhs.Name, rhs.Year);
}
auto findById(const std::map<A, int>&m, const std::string& id)
{
auto it = m.lower_bound(A{id, "", ""});
if (it != m.end() && it->first.Id == id) {
return it;
}
return m.end();
}
auto findByIdName(const std::map<A, int>&m, const std::string& id, const std::string& name)
{
auto it = m.lower_bound(A{id, name, ""});
if (it != m.end() && it->first.Id == id && it->first.Name == name) {
return it;
}
return m.end();
}
auto findByIdNameYear(const std::map<A, int>&m,
const std::string& id,
const std::string& name,
const std::string& year)
{
return m.find(A{id, name, year});
}
If you prefer to use std::vector, you may use std::find_if like that:
std::vector<A> as = /*...*/;
auto it = std::find_if(as.begin(), as.end(),
[&](const A& a){ return a.Id = id && a.Name = name;} );
if (it == as.end()) {
// Not found
} else {
// use *it as matching A.
}
Related
Can a std::map's or std::unordered_map's key be shared with part of the value? Especially if the key is non-trivial, say like a std::string?
As a simple example let's take a Person object:
struct Person {
// lots of other values
std::string name;
}
std::unordered_map<std::string, std::shared_ptr<Person>> people;
void insertPerson(std::shared_ptr<Person>& p) {
people[p.name] = p;
// ^^^^^^
// copy of name string
}
std::shared_ptr<Person> lookupPerson(const std::string& name) const {
return people[name];
}
My first thought is a wrapper around the name that points to the person, but I cannot figure out how to do a lookup by name.
For your purpose, a std::map can be considered a std::set containing std::pair's which is ordered (and thus efficiently accessible) according to the first element of the pair.
This view is particularly useful if key and value elements are partly identical, because then you do not need to artificially separate value and key elements for a set (and neither you need to write wrappers around the values which select the key).
Instead, one only has to provide a custom ordering function which works on the set and extracts the relevant key part.
Following this idea, your example becomes
auto set_order = [](auto const& p, auto const& s) { return p->name < s->name; };
std::set<std::shared_ptr<Person>, decltype(set_order)> people(set_order);
void insertPerson(std::shared_ptr<Person>& p) {
people.insert(p);
}
As an alternative, here you could also drop the custom comparison and order the set by the addresses in the shared pointer (which supports < and thus can be used directly in the set):
std::set<std::shared_ptr<Person> > people;
void insertPerson(std::shared_ptr<Person>& p) {
people.insert(p);
}
Replace set by unordered_set where needed (in general you then also need to provide a suitable hash function).
EDIT: The lookup can be performed using std:lower_bound:
std::shared_ptr<Person> lookupPerson(std::string const& s)
{
auto comp = [](auto const& p, auto const& s) { return p->name < s; };
return *std::lower_bound(std::begin(people), std::end(people), s, comp);
}
DEMO.
EDIT 2: However, given this more-or-less ugly stuff, you can also follow the lines of your primary idea and use a small wrapper around the value as key, something like
struct PersonKey
{
PersonKey(std::shared_ptr<Person> const& p) : s(p->name) {}
PersonKey(std::string const& _s) : s(_s) {}
std::string s;
bool operator<(PersonKey const& rhs) const
{
return s < rhs.s;
}
};
Use it like (untested)
std::map<PersonKey, std::shared_ptr<Person> > m;
auto sptr = std::make_shared<Person>("Peter");
m[PersonKey(sptr)]=sptr;
Lookup is done through
m[PersonKey("Peter")];
Now I like this better than my first suggestion ;-)
Here's an alternative to davidhigh's answer.
struct Person {
// lots of other values
std::string name;
}
struct StrPtrCmp {
bool operator()(const std::string* a, const std::string* b) const {
return *a < *b;
}
}
std::map<const std::string*, std::shared_ptr<Person>, StrPtrCmp> people();
void insertPerson(std::shared_ptr<Person>& p) {
people[&(p.name)] = p;
}
std::shared_ptr<Person> lookupPerson(const std::string& name) const {
return people[&name];
}
And a few edits to make it work with std::unordered_map:
struct StrPtrHash {
size_t operator()(const std::string* p) const {
return std::hash<std::string>()(*p);
}
};
struct StrPtrEquality {
bool operator()(const std::string* a, const std::string* b) const {
return std::equal_to<std::string>()(*a, *b);
}
};
std::unordered_map<const std::string*, std::shared_ptr<Person>, StrPtrHash, StrPtrEquality> people();
I am reading a object from a database of type Foo, as defined below. This object is a vector of Foo Members, where a Foo Members consists of a string id and a container object.
typedef std::pair<std::string, Container> FooMember;
typedef std::vector<FooMember> Foo;
I wish to iterate over a Foo object in its sorted form, where sorting is done with respect to the id. To do this I am using the following function to create first a sorted version of the object. As you can see, the object is sorted in a case insensitive manner. Is there a better way for me to iterate over this object compared to how I am currently doing it?
Foo sortedFoo(Foo& value) const {
Foo returnValue;
returnValue.reserve(value.size());
// use a map to sort the items
std::map<std::string, FooMember> sortedMembers;
{
Foo::iterator i = value.begin();
Foo::iterator end = value.end();
for(; i!=end; ++i) {
std::string name = i->first;
boost::algorithm::to_lower(name);
sortedMembers[name] = *i;
}
}
// convert the map to a vector of its values in sorted order
std::map<std::string, FooMember >::iterator i = sortedMembers.begin();
std::map<std::string, FooMember >::iterator end = sortedMembers.end();
for(; i!=end; ++i) {
returnValue.push_back(i->second);
}
return returnValue;
}
Yes: Copy the vector, then use std::sort with a custom comparison predicate:
struct ByIdCaseInsensitive {
bool operator ()(const FooMember& lhs, const FooMember& rhs) const {
return boost::algorithm::to_lower_copy(lhs.first) <
boost::algorithm::to_lower_copy(rhs.first);
}
};
Way more efficient than filling a map, and then copying back to a vector.
The predicate would be even better if it used a proper Unicode collation algorithm, but that isn't available in the standard library or Boost.
You can use std::sort
#include <algorithm>
bool comparator(const FooMember& i, const FooMember& j)
{
std::string str1 = i.first;
boost::algorithm::to_lower(str1);
std::string str2 = j.first;
boost::algorithm::to_lower(str2);
return (str1 < str2);
}
void sortFoo(Foo& value) {
std::sort (value.begin(), value.end(), comparator);
}
Or, you can keep Foo objects in a std::map<std::string, Foo> from the beginning so they remain always sorted.
The best way would be to use std::sort with a custom comparator for FooMembers:
bool cmp(const FooMember& lhs, const FooMember& rhs);
Foo sortedFoo(const Foo& value) const
{
Foo tmp = value;
return std::sort(tmp.begin(), tmp.end(), cmp);
}
where the comparison can be implemented with the help of std::lexicographical_compare and tolower:
#include <cctype> // for std::tolower
bool ci_cmp(char a, char b)
{
return std::tolower(a) < std::tolower(b);
}
#include <algorithm> // for std::sort, std::lexicographical_compare
bool cmp(const FooMember& lhs, const FooMember& rhs)
{
return std::lexicographical_compare(lhs.first.begin(),
lhs.first.end(),
rhs.first.begin(),
rhs.first.end(),
ci_cmp);
}
You can also use std::sort with a lambda expression:
std::sort(value.begin(), value.end(), [](const FooMember &lhs, const FooMember &rhs)
{
std::string str1 = i.first, str2 = j.first;
boost::algorithm::to_lower(str1);
boost::algorithm::to_lower(str2);
return str1 < str2;
});
Or use the version provided by erelender. It's up to you.
Semantically std::vector<std::pair<T,U> > is a std::map<T,U> (but implementations are usually different). If you can re-design Foo, you probably better do it. As side effect, you will get sorting for free.
typedef std::map<std::string, Container> Foo;
I have:
struct MyStruct
{
char* name;
int* somethingElse;
};
And I need to find in a std::vector<MyStruct*> an element (by using std::find_if) whose name is "XYZ" ... but ... the Predicate of std::find_if (if I have managed to understand it correctly) is a plain function, and it takes in a MyStruct pointer and I have no idea where I can specify the extra "XYZ" value to be used in the comparison.
So, how can I use std::find_if or this purpose? (Obviously, looking for a nice solution, not a global variable, or just walk through the list, ....)
Thanks, f
You can use a functor for this (hope I didn't get anything wrong, as I typed it in the browser):
class finder
{
const char* name;
public:
finder(const char* _name): name(_name) {}
bool operator()(MyStruct* elem) {return strcmp(elem->name, name) == 0;}
};
finder f("sample");
std::find_if(myvector.begin(), myvector.end(), f);
If you use C++11 and lambda:
std::vector<MyStruct> mystructus;
std::find_if(mystructus.begin(), mystructus.end(),
[](const MyStruct& ms){ return ms.name == std::string("XYZ"); } );
You have two options, either use functors or lamdas.
Using functors, you create a new class (or structure) whose constructor takes the string you want to search for, then it has an operator() function that is called by std::find_if:
class my_finder
{
std::string search;
public:
my_finder(const std::string& str)
: search(str)
{}
bool operator()(const MyStruct* my_struct) const
{ return search == my_struct->name; }
};
// ...
std::find_if(std::begin(...), std::end(...), my_finder("XYZ"));
The second using lambdas is less code, but requires recent version of the compiler that can handle C++11 lambdas:
std::find_if(std::begin(...), std::end(...), [](const MyStruct* my_struct)
{ return std::string("XYZ") == my_struct->name; });
The last example can even be generalized further:
using namespace std::placeholders; // For `_1` used below in `std::bind`
// Declare a "finder" function, to find your structure
auto finder = [](const MyStruct* my_struct, const std::string& to_find) {
return to_find == my_struct->name;
};
auto xyz = std::find_if(std::begin(...), std::end(...), std::bind(finder, _1, "XYZ"));
auto abc = std::find_if(std::begin(...), std::end(...), std::bind(finder, _1, "ABC"));
This way the lambda can be reused.
Predicate is anything, that can have operator () applied to it (with the expected argument(s) and returns something convertible to bool). A pointer to function is such thing, but so is an object that defines operator().
You need to provide a predicate like this:
struct Comparator
{
Comparator(const char* find) : m_find(find){}
bool operator()(MyStruct* p) const
{
return strcmp(p->name, m_find) == 0;
}
const char* m_find;
};
Then you can std::find_if like this:
vector<MyStruct*>::iterator iter = std::find_if(vec.begin(), vec.end(), Comparator("XYZ"));
if(iter != vec.end())
{
MyStruct* p = *iter;
}
Or if your compiler supports C++11 you can use lambdas and get rid of the predicate functor:
auto it = std::find_if(vec.begin(), vec.end(), [](MyStruct* p) { return strcmp(p->name, "XYZ") == 0;});
I'm very new to c++ and I'm trying to find a way to search a vector of structs for a struct with a certain member data.
I know this would work with simple types in the vector
std::find(vector.begin(), vector.end(), item) != vector.end()
But lets say I have a struct like this:
struct Friend
{
string name;
string number;
string ID;
};
and a vector like this:
vector<Friend> friends;
Then the vector is filled with friends.
Let's say I want to search for a friend with a certain ID, and cout the details. Or delete the certain struct from the vector. Is there a simple way to do this?
This can be done with std::find_if and a search predicate, which can be expressed as a lambda function if you have C++11 (or C++0x) available:
auto pred = [](const Friend & item) {
return item.ID == 42;
};
std::find_if(std::begin(friends), std::end(friends), pred) != std::end(friends);
To use an ID given as a variable, you have to capture it in the lambda expression (within the [...]):
auto pred = [id](const Friend & item) {
return item.ID == id;
};
std::find_if(std::begin(friends), std::end(friends), pred) != std::end(friends);
If you don't have C++11 available, you have to define the predicate as a functor (function object). Remy Lebeau's answer uses this approach.
To remove elements matching the criteria as defined by the predicate, use remove_if instead of find_if (the rest of the syntax is the same).
For more algorithms, see the STL <algorithm> reference.
Use std::find_if(). #leemes and #AndyProwl showed you how to use it in a C++11 compiler. But if you are not using a C++11 compiler, then you can use it like this instead, which defines a functor comparing the ID of a given item with a previously specified ID in its constructor:
class MatchesID
{
std::string _ID;
public:
MatchesID(const std::string &ID) : _ID(ID) {}
bool operator()(const Friend &item) const
{
return item.ID == _ID;
}
};
std::find_if(vector.begin(), vector.end(), MatchesID("TheIDHere")) != vector.end();
If you have other classes in your project which use IDs, you can make this functor templated:
template<typename IDType>
class MatchesID
{
IDType _ID;
public:
MatchesID(const IDType &ID) : _ID(ID) {}
template<class ItemType>
bool operator()(const ItemType &item) const
{
return item.ID == _ID;
}
};
std::find_if(vector.begin(), vector.end(), MatchesID<std::string>("TheIDHere")) != vector.end();
You can use std::find_if in combination with functors (if you are working with C++98) or lambdas (if you are using C++11, which I will assume):
using namespace std;
int ID = 3; // Let's say...
auto it = find_if(begin(vector), end(vector), [=] (Friend const& f) {
return (f.ID == ID);
});
bool found = (it != end(vector));
If you want to find an element in STL container, use std::find or std::find_if algorithms
With C++03, you need to overload operator== for std::find
bool operator==(const Friend& lhs, const Friend& rhs)
{
return lhs.ID == rhs.ID;
}
if (std::find(friends.begin(), friends.end(), item) != friends.end())
{
// find your friend
}
OR C++11 with lambda:
std::find_if(friends.begin(), friends.end(), [](Friend& f){ return f.ID == "1"; } );
If you want to remove a certain element, use std::remove_if
std::remove_if(friends.begin(), friends.end(),
[](Friend& f){ return f.ID == "1"; });
I am trying to understand how to find an item in a list of pointers in C++, using std::find
If I had for example:
std::list<string> words;
std::string word_to_be_found;
I could just search like this:
std::list<string>::iterator matching_iter = std::find(words,begin(), words.end(), word_to_be_found)
but what if I have a lsit of pointers?
std::list<string *> words;
the above syntax will not work anymore. Can I do it some similar way?
thanks!
You can pass a predicate to the std::find_if function:
bool pointee_is_equal(const std::string& s, const std::string* p) {
return s == *p;
}
// ...
std::list<string>::iterator matching_iter =
std::find_if(words,begin(), words.end(),
std::bind1st(pointee_is_equal, word_to_be_found));
In C++11 this becomes much easier, thanks to lambdas:
auto matching_iter = std::find_if(words,begin(), words.end(),
[&word_to_be_found](const std::string* p) {
return word_to_be_found == *p;
});
Provide your own predicate:
struct comparator
{
bool operator()(std::string const* item)
{
return toFind == *item;
}
std::string toFind;
};
comparator cinst = { word_to_find };
std::list<string*>::iterator matching_iter = std::find_if(words,begin(), words.end(), cinst)
You want to use std::find_if() instead, and supply it a functor to do the comparisons.