How to sort QMap<QString, myStruct>? - c++

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.

Related

How can i compare two properties of struct in a single SET?

My code is as below.
I have as struct ABC and i have set g_ABCSet to compare the id.
struct ABC
{
CString name;
byte id[2];
}
typedef shared_ptr<ABC> ABC_PTR;
std::set<ABC_PTR, CompareABC> g_ABCSet;
class ComparePager{
public:
bool operator()(const ABC_PTR& m1, const ABC_PTR& m2) const {
if (m1->id[0] == m2->id[0]){
return m1->id[1] < m2->id[1];
}
return m1->id[0] < m2->id[0];
}
}
I try to search in set as below comparing the id
static ABC_PTR ABCptr(new ABC);
//Assume ABCptr have some valid ID
auto it = g_ABCSet.find(ABCptr);
if (it == g_ABCSet.end())
{
//not found
}
else
{
//found one
}
My query here is can i use the same set to compare the "Cstring name" in the ABC struct.
If YES HOW ??
IF NO , DO i need to make same new set ,overlaod operator to comparing Cstring and insert all same pointers to new set also ??
No you cannot use a single std::set.
Why: Because the set requires the keys to be in 'strict ordering'. Most likely the set uses a tree structure to store it's items and the tree is sorted by the given comparator.
This means also that if you insert multiple items with different names and identical ids, only one items is stored at all (because the Comparator says they are all identical)
You can use std::find_if to search for Cstring name:
CString searchName = "...";
auto it = std::find_if(
g_ABCSet.begin(),
g_ABCSet.end(),
[&](const ABC_PTR& ptr) {
return ptr->name == searchName;
});
If you have a large set of items in g_ABCSet you should do as you wrote: create a second set with a Comparator for 'name'.
Tip: If you use std::array<byte, 2> id instead of byte id[2] your Comparator could be as simple as
class ComparePager{
public:
bool operator()(const ABC_PTR& m1, const ABC_PTR& m2) const {
return m1->id < m2->id;
}
}
Maybe you better use a std::map<std::array<byte, 2>, ABC_PTR> and another std::map<CString, ABC_PTR> for this job. This needs more memory (mostly because of the CString being copied from the g_ABCSet into the map) but get completly rid of the custom Comparators and you cannot accidentally use the wrong set (with the wrong Comparator)

std map find is always true

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();
}
};

How to insert unique items into vector?

I have a type called Neighbors:
typedef vector<pair<data,int>> Neighbors;
and here's data:
struct data
{
int par[PARAMETERS];
int cluster;
bool visited;
bool noise;
};
I'm trying to write a function that inserts values from _NeighborPts to NeighborPts (but only ones that aren't already in NeighborPts):
void insert_unique(Neighbors* NeighborPts, const Neighbors& _NeighborPts)
{
Neighbors_const_it _it = _NeighborPts.begin();
while(_it != _NeighborPts.end())
{
if(/* _it->first.par isn't in *NeighborPts */)
NeighborPts->push_back(*_it);
++_it;
}
}
and i already have a function equal() which checks if 2 pars are equal.
So do i have to iterate through NeighborPts in a while loop and check if the item is found? or could i use some built-in find or find_if function to do that for me?
You can maintain a sorted vector. Use the lower_bound function from C++ algorithms to locate the insert position each time. If the element at the insert position is equal to the insert element then you have a duplicate.
The performance of this will be pretty good unless the vector grows too large. The point at which you're better off using a set or a unordered_set varies and you'd need to benchmark to find it.
Your current solution with vector will run in O(N^2) time, which is not efficient.
For efficient solution an associative container will be great - such as std::set .
Also you will need to have some "operator less" (instead of "equal ()"), to pass to the function.
template < class T, // set::key_type/value_type
class Compare = less<T>, // set::key_compare/value_compare
class Alloc = allocator<T> // set::allocator_type
> class set;
So you need to provide compare class
struct data_compare {
bool operator() (const data& lhs, const data& rhs) const{
//...
}
};
set<int64_t, data_compare> exising_items;
You may define such a function, or override "operator <" in struct data.
insert all "data" from "_NeighborPts" into a set - O(N*log(N)) time
std::set other_items;
in a loop - iterate _NeighborPts and insert data elements
other_items.insert (_NeighborPts [i]);
std::set my_items;
in a loop - iterate _NeighborPts and insert data elements
my_items.insert (NeighborPts [i]);
Now you need to compare between the 2 sets:
You can do it using std::set_intersection
. or construct a simple loop on the set "my_items"
if the current element in other_items isn't in _my_items, insert it in "NeighborPts"
this solution will run in O(Nlog(N)) time
There is no getting around iterating over the items in _NeighborPts.
As long as you are using an std::vector, there is no getting around the check to determine whether an item is in NeighborPts before inserting in it.
You can make the code a little bit easier to read by using std::for_each and a functor.
struct UniqueItemInserter
{
UniqueItemInserter(Neighbors* neighborsIn) : neighbors(neighborsIn) {}
void operator(pair<data,int> const& item)
{
if ( std::find(neighbors->begin(), neighbors->end(), item) != neighbors->end() )
{
neighbors->push_back(item);
}
}
Neighbors* neighbors;
};
void insert_unique(Neighbors* NeighborPts, const Neighbors& _NeighborPts)
{
std::for_each(_NeighborPts.begin(), _NeighborPts.end(), UniqueItemInserter(NeighborPts));
}

Overloaded [] operator for hash table to a vector

I am working on a simple hash table in C++. I have methods to insert, delete, and search the hash table for the specified key. I know that the C++ map STL container can handle my situation, but I would kind of like to code my own as an educational exercise.
Basically I have a hash table that will take a single string and map it to a vector of other strings. This is easy to do in a method because calling a .Add() or .Delete() will behave as expected. I would however like to create an overloaded [] operator to the class that is able to do these operations on the vector.
For example, if I want to add an item to the vector I can write something like this:
hashTable[string1] = newString;
This will set the new string as a member of my vector. The same can be said for delete and search.
hashTable[string1] = "";
cout << hashTable[string1] << endl;
My major problem is that I do not know how to overload the [] operator to gain this functionality. I have this function coded up right now. It works on a basic 1 to 1 string match, but not on a string to vector match.
//Return a reference to a vector to update then reassign?
vector& HashClass::operator[](const string index)
{
assert(size >= 0 && size < maxSize);
Hash(key);
return hashTable[index];
}
I think I'm most stuck on the idea of having a vector return that later needs to be assigned. As the user, I would find this kludgy.
This question is closely related to another question: what behavior do
you want when you access a non-existant value other than in an
assignment? In other words, what do you want to happen when you write:
std::cout << hashTable[string] << std::endl;
and string is not present in the table?
There are two possible approaches: you can consider it an error, and
throw an exception, or abort, or something similar; or you can return
some sort of default, built with the default constructor, or provided by
the client earlier.
The standard map and unordered_map take the second approach, using the
default constructor to construct a new value. This allows a very simple
solution: if operator[] isn't present, you insert it, initializing it
with the default value. Then you return a reference to it;
hashTable[string] = newString; assigns through the reference to an
already existing value.
In many use cases, the first approach will be preferable (perhaps with a
contains function, so you can test up front whether the operator[]
will find something or not). To implement the first approach, you must
first implement specific functions for each type of access:
template <typename Key, typename Value>
class HashTable
{
public:
Value* get( Key const& key ) const;
void set( Key const& key, Value const& value );
};
(I generally make these public; there's no reason to forbid their use by
a client.)
Then, you define operator[] to return a proxy, as follows:
template <typename Key, typename Value>
class HashTable
{
public:
class Proxy
{
HashTable* myOwner;
Key myKey;
public:
Proxy( HashTable* owner, Key const& key )
: myOwner( owner )
, myKey( key )
{
}
operator Value const&() const
{
Value const* result = myOwner->get( myKey );
if ( result == NULL ) {
// Desired error behavior here...
}
return *result;
}
Proxy const& operator==( Value const& value ) const
{
myOwner->set( myKey, value );
return *this;
}
};
Value* get( Key const& key ) const;
void set( Key const& key, Value const& value );
Proxy operator[]( Key const& key )
{
return Proxy( this, key );
}
};
Thus, when you write:
hashTable[key] = newString;
, the proxy's operator= will call hashTable.put( key, newString );
in other contexts, however, it will call the implicit type conversion on
the proxy, which calls hashTable.get( key ).
In some cases, even if you desire to return a default value, it may be
preferable to use this solution: the get function is not required to
insert anything into the hash table, so the table doesn't fill up with
all of the misses, and you can overload the operator[] on const, so
you can use it on a const hash table as well. Also, it doesn't
require the value type to have a default constructor.
It does have one disadvantage with respect to the solution used in the
standard; since you can't overload operator., you can't make the proxy
behave like a reference, and things like:
hashTable[string].someFunction();
don't work. A work-around is to overload operator-> in the proxy, but
this leads to a somewhat unnatural syntax:
hashTable[string]->someFunction(); // But the hash table contains
// values, not pointers!!!
(Don't be mislead by the implicit conversion to a reference. An
implicit conversion will not be considered for a in an expression
a.b.)
In C++, [] access to associative containers is generally given the semantics of default-constructing an object of the mapped type, inserting it with the key, and returning a reference to the inserted mapped object.
So your operator[] would be implemented as:
string& HashClass::operator[](const string index)
{
assert(size >= 0 && size < maxSize);
Hash(key);
vector &v = hashTable[index];
if (index in v) {
...
} else {
v.push_back(string());
return v.back();
}
}

Avoiding Helper Functions for Doing Comparisons

Say I have a type with a member function:
class Thing {
std::string m_name;
public:
std::string & getName() {
return m_name;
}
};
And say I have a collection of that type:
std::vector<Thing> things;
And I want to keep the things in order by name. To do that, I use std::lower_bound to figure out where to put it:
bool thingLessThan(Thing const& thing, std::string const& name) {
return thing.getName() < name;
}
void addThing(std::string const& name) {
vector<Thing>::iterator position = lower_bound(
things.begin(), things.end(),
name,
thingLessThan);
if (position == things.end() || position->getName() != name) {
position = things.insert(position, Thing());
position->getName() = name;
}
}
Is there a way to do the same thing as the thingLessThan function without actually creating a function, perhaps using std::mem_fun, std::less, etc?
Other than a lambda you can simply define an operator< which adheres to strict weak ordering to allow a container of your object to be comparable by STL algorithms with the default predicate std::less
class whatever
{
public:
bool operator<(const whatever& rhs) const { return x < rhs.x; }
private:
int x;
};
std::vector<whatever> v;
std::sort(v.begin(), v.end());
Sure. You can use a lambda expression (assuming your compiler supports it):
vector<Thing>::iterator position = lower_bound(
things.begin(), things.end(),
name,
[](Thing const& thing, std::string const& name) { return thing.getName() < name; });
Of course, an alternative option is just to define operator< for the class, then it will be used by default, if you don't specify another comparer function for std::lower_bound.
Depending on what your purpose is? If you just like the syntactic niceness of not declaring something to be used in one place, use lambda expressions to create an anonymous function.
You can overload operator<() and use std::less<T> if you don't want to write predicates contantly. Also you can use lambda-expressions, which would be much nicer, because operator<() is logically connected only with things, that can be put in some order in obvious ways, like numbers or strings.
If you use a std::map, the strings will be placed in alphabetical order automatically. If you want to modify the ordering further, create your own key comparison function. I think this would be the simplest option.
To use a std::list, you can write your own comparison code inside of the addThing() function that goes through the list looking at each string and inserts the new one at the appropriate place.