I have this structure
struct Event {
const string event;
const int order;
Event(const string& _event, const int& _order):event(_event),order(_order) {}
};
struct EventCompare {
bool operator()(const Event& lhs, const Event& rhs)
{
return (lhs.order < rhs.order);
}
};
which I would like to use in a set:
set<Event, EventCompare> events;
I do know that sets doesn't allow duplicates. However, I would like to define duplicates as two instance of structs with equal events regardless of their orders, in other words, A = B iff A.event == B.event. This definition has to affect the way the set works, which means that, for example, the set has to ignore Event("holiday", 1), if it already contains Event("holiday", 0).
How can I do that? I've tried to add
if (lhs.event == rhs.event)
return false;
in my EventCompare, but that didn't work. Will using pair instead of struct help anyhow?
If under the conditions you specified they are considered to be equal, then it's obvious that the result of the < comparison would be false. One is not less than the other, they are considered to be equal. The comparison operator, for the purpose of being used with associative containers, only needs to indicate if one instance is "less" than the other. Since, under these circumstances, they are considered to be equal, neither one is less than the other.
Therefore:
struct EventCompare {
bool operator()(const Event& lhs, const Event& rhs)
{
if (lhs.event == rhs.event)
return false;
return (lhs.order < rhs.order);
}
};
However, this does not address the situation where two instances of the Event object have the same order, but different Events. If such situation is cannot arise, you don't have to worry about it. If it can, simply decide what their ordering would be, and set the return value of the comparison operator, in that case, accordingly.
The closest you can use is:
struct EventCompare {
bool operator()(const Event& lhs, const Event& rhs)
{
if (lhs.event == rhs.event)
return false;
return (lhs.order < rhs.order);
}
};
However, the compare criteria you are asking for does not meet the strictly weak ordering, which is required to put objects in a std::set.
Let's you have three objects with the following data:
obj1 = {"foo", 200}
obj2 = {"bar", 300}
obj3 = {"foo", 400}
If you add objects to the set in the order obj1, obj2, obj3, you will see only obj1 and obj2, in that order, in the set.
If you add objects to the set in the order obj2, obj3, obj1, you will see only obj2 and obj3, in that order, in the set.
Not only do you get different objects in the set depending on which object is added to the set first but even the objects appear in different order based on which object was added to the set first. I can only see problems in the future if you follow this strategy.
I think you should take a fresh look at your requirements and look for a cleaner solution. I am not able to suggest a solution without a deeper understanding of what your are trying to do.
A set doesn't look for equality. It only checks for an ordering.
Since you want two events to be equal if the event is the same, that means that neither comes before the other, and your comparison function should return false in that case.
bool operator()(const Event& lhs, const Event& rhs) const {
return lhs.event != rhs.event && lhs.order < rhs.order;
}
However, this won't work since it no longer defines a strict weak ordering, since you can have events where A < B and B < C but !(A < C) if A and C have matching event strings but B's order is between A's and C's.
So no, you can't use a set to store elements where a 2nd non-ordering attribute overrides the ordering one. You'd have to change the ordering to be based on event, but then you won't be able to look things up based on the order.
You could use a map to map the event strings to the order value used to store them into the set. Then you check the map to see if it is already there, and decide which element to keep in the set. Otherwise update both the set and map with the new entry.
I seem to be missing something blindingly obvious. But surely all you need to do is compare according to your requirements?
struct EventCompareName
{
bool operator()(const Event& lhs, const Event& rhs)
{
// If you want to compare by event and not order, then do so.
return (lhs.event < rhs.event);
// Rather than comparing by order and not event.
//return (lhs.order < rhs.order);
}
};
std::set<Event, EventCompareName> events;
Of course, you might also want to compare by order in some cases (even though your question gives absolutely zero indication of that requirement). In which case:
struct EventCompareNameOrder
{
bool operator()(const Event& lhs, const Event& rhs)
{
if (lhs.event != rhs.event)
return (lhs.event < rhs.event);
return (lhs.order < rhs.order);
}
};
std::set<Event, EventCompareNameOrder> allEvents;
Related
I have a QMap<QString, myStruct> with
myStruct {
QString firstname;
QString lastname;
QString status;
}
How can I sort this QMap according to priority order: status then firstname then lastname?
As far as I understand, you'd like to retrieve the values of the map sorted in the mentioned way, but still have access to the key. Right?
Quickly speaking, a map is a collection of <key, value> pairs automatically sorted by key, then you may try a list of <value, key> pairs manually sorted by value instead. Something like QList<QPair<myStruct, QString>>, while overriding the operator< for myStruct.
struct myStruct {
QString firstname;
QString lastname;
QString status;
bool operator<(const myStruct& o) const {
return std::tie(status, firstname, lastname) <
std::tie(o.status, o.firstname, o.lastname);
}
};
QMap<QString, myStatus> map; // your original map
QList<QPair<myStatus, QString>> inv;
// Populate the inverted list
for (auto k : map.keys()) {
inv.append(QPair<myStatus, QString>(map[k], k));
}
std::sort(std::begin(inv), std::end(inv));
for (auto p : inv) {
qDebug() << p.first.status << p.first.firstname << p.first.lastname << p.second;
}
Of course, it is a one-time use structure that doesn't keep updated with your original map, but you mentioned that the map is fixed (constant?) so it may not be a problem then.
BTW, a QMap can be used for the inverse look-up but only in the case the values of the myStruct part are also unique (so they can be used also as a key), otherwise you may overwrite values when constructing the inverse map.
Note: The std::tie is used just to simplify the sorting condition for tuples (so you'd need to include <tuple>).
UPDATE
Answering one of your comments: Yes, you can also specify your own comparison predicate and then avoid overriding the operator<, but I think it is harder to read and less re-usable:
std::sort(std::begin(inv), std::end(inv),
[](const QPair<myStatus, QString>& lhs, const QPair<myStatus, QString>& rhs) {
return std::tie(lhs.first.status, lhs.first.firstname, lhs.first.lastname) <
std::tie(rhs.first.status, rhs.first.firstname, rhs.first.lastname);
});
Of course, you can implement that comparison lambda as you want, I've used the std::tie again to simplify the logic in the post. The downside is that if you need to generate the inverse map in several places you'd have to repeat the lambda expression everywhere (or create a function to create the inverse map of course).
As a side note and in case you are curious, lhs and rhs refers to left-hand side and right-hand side respectively, in this case they are used as lhs < rhs by the sorting algorithm for comparing the elements.
Finally, if you'd want to avoid the std::tie you'd have to make the comparisons manually (code below modifies the operator< of the first version):
bool operator<(const myStruct& o) const {
if (status < o.status) return true;
if (status > o.status) return false;
// status == o.status, move to next attribute
if (firstname < o.firstname) return true;
if (firstname > o.firstname) return false;
// firstname== o.firstname, move to next attribute
if (lastname < o.lastname) return true;
if (lastname > o.lastname) return false;
return false; // are equal
}
You can't sort a QMap manually, you'll have to use a QList (or QVector) for that and use std::sort on it. Use QMap::values() to extract the values (structs) from the map into a list, then implement a compare function/method and call it with std::sort. See cbucharts answer for some hints how to do this.
Keeping map and list in sync when the values change is a different issue, if this is a requirement you should create a separate question, adding a MCVE and more details on what you tried.
How does the std::set<T> container check if two objects are unique? I tried overriding the equality operators (==), but it didn't work.
The reason I want to do this is that I have let's say a class Person and I specify that my Person is the same person if they have the same name (maybe even birthdate, address, etc.).
In ccpreference.com, they write the following (which is a bit unclear to me):
Everywhere the standard library uses the Compare concept, uniqueness
is determined by using the equivalence relation. In imprecise terms,
two objects a and b are considered equivalent (not unique) if neither
compares less than the other: !comp(a, b) && !comp(b, a).
I assume, that this question also expands to other STL containers and even algorithms (maybe even to the whole STL). So if in future, I want to use the function std::find, I would be looking up the name of the person and not the object itself. Is this correct?
EDIT
I want to add some example code.
// My operator overloading comparing two strings.
bool operator==(Node & rhs) const {
return this->name.compare(rhs.name);
}
Then, in the UnitTest I add twice an object with the same name into the set. It is added twice (but should be the same according to the operator==.
void test_adding_two_identical_nodes() {
// The pool is a set<Node> inside
model::Node_Pool pool{};
pool.store_node(model::Node{"Peter"});
pool.store_node(model::Node{"Peter"});
// Should be only 1 because the same node should be added once into a set.
ASSERT_EQUAL(1, pool.size());
}
std::set<T> doesn't compare using ==. It compares, by default, using std::less<T>. In turn std::less<T> uses, by default, the operator <.
One way to implement a set is to override operator<, like so:
#include <set>
#include <cassert>
struct Person {
const char *name;
int uid;
};
bool operator<(const Person& a, const Person& b) {
return a.uid < b.uid;
}
int main () {
Person joe = {"joseph", 1};
Person bob = {"robert", 2};
Person rob = {"robert", 3};
Person sue = {"susan", 4};
std::set<Person> people;
people.insert(joe);
people.insert(bob);
people.insert(rob);
assert(people.count(joe) == 1);
assert(people.count(bob) == 1);
assert(people.count(rob) == 1);
assert(people.count(sue) == 0);
Person anonymous_3 = {"", 3};
assert( std::strcmp(people.find(anonymous_3)->name, "robert") == 0);
}
Alternatively, one can pass a compare operator as a template parameter when declaring the set. In the example above, this might be the compare operator:
struct Person_Compare {
bool operator()(const Person& a, const Person& b) const {
return a.uid < b.uid;
}
};
And the std::set declaration might look like this:
std::set<Person, Person_Compare> people;
The rest of the example is unchanged.
First of all, don't override comparison operators to compare anything but TOTAL equivalence. Otherwise you end up with a maintenance nightmare.
That said, you'd override operator <. You should instead give set a comparitor type though.
struct compare_people : std::binary_function<person,person,bool>
{
bool operator () ( person const& a, person const& b) const { return a.name() < b.name();
};
std::set<person, compare_people> my_set;
I couldn't find any information on how to order objects in a priority queue. I tried this:
class Person {
...
public:
bool operator<(const Person& p) {
return age < p.age;
}
}
int main() {
priority_queue<Person*> people;
people.push(new Person("YoungMan", 21));
people.push(new Person("Grandma", 83));
people.push(new Person("TimeTraveler", -5000));
people.push(new Person("Infant", 1));
while (!people.empty()) {
cout << people.top()->name;
delete people.top();
people.pop();
}
And it's supposed to give priority based on age (older people get higher priority, and thus leave the queue first), but it doesn't work. But I'm getting this output:
Infant
Grandma
TimeTraveler
YoungMan
And I have no idea what this is ordered by, but it's definitely not age.
priority_queue<Person*> actually orders based on comparing the memory addresses of Person object using the comparator std::less<Person*>.
Declare a priority_queue<Person> instead to order based on the operator< you provided.
Or if you insist on using pointers (for some reason) then declare as:
auto age_comp = [](const std::unique_ptr<Person>& lhs, const std::unique_ptr<Person>& rhs) -> bool {
return *lhs < *rhs;
};
std::priority_queue<std::unique_ptr<Person>, std::vector<std::unique_ptr<Person>>,
decltype(age_comp)> people(age_comp);
// note: must pass age_comp to std::priority_queue constructor here as
// lambda closure types have deleted default constructors
Note that this is using smart pointers not raw pointers, the former are much more commonly used in modern C++ - don't use raw pointers unless you have a very good reason to.
Also, operator< of Person should be const specified as it shouldn't change the Person object it belongs to at any point - the comparator of std::priority_queue expects the const and will likely throw an error if the operator< does not have const specification. So, alter operator< to:
bool operator<(const Person& p) const {
return age < p.age;
}
I have a problem using the std::map, specifically when using find.
I have the following code.
class MyClass
{
update(const QVariant&);
QVariant m_itemInfo;
std::map<QVariant, int> m_testMap;
}
void update(const QVariant& itemInfo)
{
if(m_itemInfo != itemInfo)
{
// The items are not equal
m_itemInfo = itemInfo;
}
if(m_testMap.find(itemInfo) == m_testMap.end())
{
// TestMap doesnt contain key itemInfo.
m_testMap.insert(std::make_pair(itemInfo, 1));
}
// More code
}
The function update is called several times (with different itemInfo objects) in my code. Now when I start to debug it, I see that the first time update is called, both the first and the second if loop are entered. So far so good. However the second time update is called I do see that the first if loop is called, but the second is skipped! What am I missing here?
I guess the problem that the first and second QVariants that you pass to your Update method have different type (for example, bool and uint). std::map::find doesn't use !=operator to compare keys, it uses operator < (less) by default. If two compared QVariant values have different types operators != and < may work contradictory.
std::map::find compares keys in the following way:
Two keys are considered equivalent if the container's comparison object returns false reflexively (i.e., no matter the order in which the elements are passed as arguments).
i.e. std::map::find considers that v1 is equal to v2
if(!(v1<v2) && !(v2>v1)) { //is TRUE !!!
}
To solve your problem, you should define a less comparison for std:map.
class QVariantLessCompare {
bool operator()(const QVariant& v1, QVariant& v2) const {
// ==== You SHOULD IMPLEMENT appropriate comparison here!!! ====
// Implementation will depend on type of QVariant values you use
//return v1 < v2;
}
};
And use QVariantCompare in a such way:
std::map<QVariant, int, QVariantLessCompare> m_testMap;
A more paradigmatic solution is to use QMap which correctly implements the comparison of most QVariant types. It won't do userTypes() out of the box, but this still might suit your application.
A cleaner version of the solution proposed by Володин Андрей, that builds, might look like:
struct QVariantLessCompare {
bool operator()(const QVariant& v1,const QVariant& v2) const
{
return v1.toInt() < v2.toInt();
}
};
I have no idea how to implement priority_queue, when comparing an objects integer field , it's is clear.
For example
bool operator()(const Toast &t1, const Toast &t2) const
{
int t1value = t1.bread * 1000 + t1.butter;
int t2value = t2.bread * 1000 + t2.butter;
return t1value < t2value;
}
This will place the objects in the heap according to the values.
The question is how to compare objects according to the bool fields? How to store several objects according to the boolean type?
For example :
vip=true, notvip=false;
Vip1 , notVip2, Vip3.
The result should be: Vip1,Vip3,notVip2;
Can you give me an idea.
Your question is a bit unclear. I'm going to assume that the structure that you want to sort is Toast.
If you simply want to prioritize Toasts based on a bool, say status, your comparison object is pretty easy:
class mycomparison
{
public:
bool operator() (const Toast& lhs, const Toast& rhs) const
{
return lhs.status < rhs.status;
}
};
Now I'm going to assume the following to expand on this, given Toast has the following bool members listed in order of most significant to least significant: vip, notvip, Vip1,Vip3, andnotVip2`, I'm also assuming that the comparison on "not" members should be inverted:
class mycomparison
{
public:
bool operator() (const Toast& lhs, const Toast& rhs) const
{
return lhs.vip < rhs.vip || lhs.vip == rhs.vip && // return true if rhs.vip is the only true or continue to test
( lhs.noVip > rhs.noVip || lhs.noVip == rhs.noVip && // return true if rhs.noVip is the only false or continue to test
( lhs.Vip1 < rhs.Vip1 || lhs.Vip1 == rhs.Vip1 && // return true if lhs.Vip1 is less than rhs.Vip1 or continue to test
( lhs.Vip3 < rhs.Vip3 || lhs.Vip3 == rhs.Vip3 && // return true if lhs.Vip3 is less than rhs.Vip3 or continue to test
lhs.notVip2 > rhs.notVip2 ) ) ); // return true if lhs.notVip2 is greater than rhs.notVip2 or return false
}
};
Note that either of these mycomparison classes works fine if the members are bools or any other type for which operator<, operator> and operator== is defined. To use mycomparision in a std::priority_queue you'll just need to pass it as the comparison object in std::priority_queue's ctor, for example: std::priority_queue< Toast > foo( mycomparison() );