Search code examples
c++qtsortingqsortqmap

How to sort QMap<QString, myStruct>?


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?


Solution

  • 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
    }