4

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?

frogatto
  • 28,539
  • 11
  • 83
  • 129
gnase
  • 580
  • 2
  • 10
  • 25
  • 11
    Perhaps you should use another container, since [`QMap`](http://doc.qt.io/qt-5/qmap.html) is always sorted *on the key!* – Some programmer dude Oct 16 '17 at 07:57
  • Actually this QMap is fixed and I can not changed any more :(. So I am thinking about another solution: because the key in my case is unique, So I would take out all the values as `QList` and then sort this `QList`, after that put the sorted list into QMap again. How do you think about that solution? – gnase Oct 16 '17 at 08:47
  • 3
    That won't help. When you insert back into the map, it will be "resorted" according to the key again. Depending on your use-case you might want to use two containers. First the map, which is sorted by the key. Then a vector (or similar) with reference-wrappers to the structure in the map. You can sort the vector any way you want. – Some programmer dude Oct 16 '17 at 08:50

2 Answers2

2

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
}
cbuchart
  • 10,847
  • 9
  • 53
  • 93
  • thanks a lot @cbuchart. Your solution is really brilliant. But there is one thing i dont understand. You overwrite `operator<`, but I dont see where it is used. Could you explain a little bit clearer? – gnase Oct 16 '17 at 15:52
  • Besides, do we have another algorithm except for `std::tie` to sort elements, so that we dont have to overwrite anything? – gnase Oct 16 '17 at 15:55
  • @gnase the `operator<` is used by the [`std::sort`](http://en.cppreference.com/w/cpp/algorithm/sort) to, well, sort the list. The [`std::tie`](http://en.cppreference.com/w/cpp/utility/tuple/tie) is used to simplify the code of the comparison, ow you'd need to contemplate all the cases manually. – cbuchart Oct 16 '17 at 18:00
  • thanks a lot @cbuchart. I understand more through your answer. I accepted it. Have a nice day! – gnase Oct 18 '17 at 06:12
1

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.

Murphy
  • 3,827
  • 4
  • 21
  • 35