web-dev-qa-db-fra.com

Itérer sur un QMap avec pour

J'ai un objet QMap et j'essaie d'écrire son contenu dans un fichier.

QMap<QString, QString> extensions;
//.. 

for(auto e : extensions)
{
  fout << e.first << "," << e.second << '\n';
}  

Pourquoi je reçois: error: 'class QString' has no member named 'first' nor 'second'

e n'est-il pas de type QPair?

44
user336635

Si vous voulez le style STL avec first et second, procédez comme suit:

for(auto e : extensions.toStdMap())
{
  fout << e.first << "," << e.second << '\n';
}

Si vous souhaitez utiliser les offres Qt, procédez comme suit:

for(auto e : extensions.keys())
{
  fout << e << "," << extensions.value(e) << '\n';
}
38
Arlen

C++ 11 range-based-for utilise le type de l'itérateur déréférencé comme type de "curseur" déduit automatiquement. Ici, c'est le type de l'expression *map.begin().
Et comme QMap::iterator::operator*() renvoie une référence à la valeur (de type QString &), la clé n’est pas accessible à l’aide de cette méthode.

Vous devez utiliser l'une des méthodes d'itérateur décrites dans la documentation, mais vous devez éviter d'utiliser 

  • keys() car il s’agit de créer une liste de clés, puis de rechercher la valeur de chaque clé, ou
  • toStdMap() car il copie tous les éléments de la carte dans un autre,

et ce ne serait pas très optimal .


Vous pouvez également utiliser un wrapper pour obtenir QMap::iterator en tant que type auto:

template<class Map>
struct RangeWrapper {
    typedef typename Map::iterator MapIterator;
    Map &map;

    RangeWrapper(Map & map_) : map(map_) {}

    struct iterator {
        MapIterator mapIterator;
        iterator(const MapIterator &mapIterator_): mapIterator(mapIterator_) {}
        MapIterator operator*() {
            return mapIterator;
        }
        iterator & operator++() {
            ++mapIterator;
            return *this;
        }
        bool operator!=(const iterator & other) {
            return this->mapIterator != other.mapIterator;
        }
    };
    iterator begin() {
        return map.begin();
    }
    iterator end() {
        return map.end();
    }
};

// Function to be able to use automatic template type deduction
template<class Map>
RangeWrapper<Map> toRange(Map & map)
{
    return RangeWrapper<Map>(map);
}

// Usage code
QMap<QString, QString> extensions;
...
for(auto e : toRange(extensions)) {
    fout << e.key() << "," << e.value() << '\n';
}

Il y a un autre wrapper ici .

40
alexisdm

Pour les personnes intéressées par les optimisations, j'ai essayé plusieurs approches, quelques micro-points de repère et je peux conclure que l'approche de style STL est nettement plus rapide .

J'ai essayé d'ajouter des entiers avec ces méthodes:

  • QMap :: values ​​()
  • Itérateur de style Java (comme indiqué dans la documentation)
  • Itérateur de style STL (comme indiqué dans la documentation)

Et je l'ai comparé avec la somme des nombres entiers d'un QList/QVector

Résultats : 

Reference vector :   244  ms
Reference list :     1239  ms

QMap::values() :     6504  ms
Java style iterator :    6199  ms
STL style iterator :     2343  ms

Code pour les intéressés: 

#include <QDateTime>
#include <QMap>
#include <QVector>
#include <QList>
#include <QDebug>

void testQMap(){
    QMap<int, int> map;
    QVector<int> vec;
    QList<int> list;

    int nbIterations = 100;
    int size = 1000000;
    volatile int sum = 0;

    for(int i = 0; i<size; ++i){
        int randomInt = qrand()%128;
        map[i] = randomInt;
        vec.append(randomInt);
        list.append(randomInt);
    }


    // Rererence vector/list
    qint64 start = QDateTime::currentMSecsSinceEpoch();
    for(int i = 0; i<nbIterations; ++i){
        sum = 0;
        for(int j : vec){
            sum += j;
        }
    }
    qint64 end = QDateTime::currentMSecsSinceEpoch();
    qDebug() << "Reference vector : \t" << (end-start) << " ms";

    qint64 startList = QDateTime::currentMSecsSinceEpoch();
    for(int i = 0; i<nbIterations; ++i){
        sum = 0;
        for(int j : list){
            sum += j;
        }
    }
    qint64 endList = QDateTime::currentMSecsSinceEpoch();
    qDebug() << "Reference list : \t" << (endList-startList) << " ms";

    // QMap::values()
    qint64 start0 = QDateTime::currentMSecsSinceEpoch();
    for(int i = 0; i<nbIterations; ++i){
        sum = 0;
        QList<int> values = map.values();
        for(int k : values){
            sum += k;
        }
    }
    qint64 end0 = QDateTime::currentMSecsSinceEpoch();
    qDebug() << "QMap::values() : \t" << (end0-start0) << " ms";


    // Java style iterator
    qint64 start1 = QDateTime::currentMSecsSinceEpoch();
    for(int i = 0; i<nbIterations; ++i){
        sum = 0;
        QMapIterator<int, int> it(map);
        while (it.hasNext()) {
            it.next();
            sum += it.value();
        }
    }
    qint64 end1 = QDateTime::currentMSecsSinceEpoch();
    qDebug() << "Java style iterator : \t" << (end1-start1) << " ms";


    // STL style iterator
    qint64 start2 = QDateTime::currentMSecsSinceEpoch();
    for(int i = 0; i<nbIterations; ++i){
        sum = 0;
        QMap<int, int>::const_iterator it = map.constBegin();
        auto end = map.constEnd();
        while (it != end) {
            sum += it.value();
            ++it;
        }
    }
    qint64 end2 = QDateTime::currentMSecsSinceEpoch();
    qDebug() << "STL style iterator : \t" << (end2-start2) << " ms";


    qint64 start3 = QDateTime::currentMSecsSinceEpoch();
    for(int i = 0; i<nbIterations; ++i){
        sum = 0;
        auto end = map.cend();
        for (auto it = map.cbegin(); it != end; ++it)
        {
            sum += it.value();
        }
    }
    qint64 end3 = QDateTime::currentMSecsSinceEpoch();

    qDebug() << "STL style iterator v2 : \t" << (end3-start3) << " ms";
}

Éditer juillet 2017: J'ai de nouveau exécuté ce code sur mon nouvel ordinateur portable (Qt 5.9, i7-7560U) et j'ai obtenu des modifications intéressantes.

Reference vector :   155  ms 
Reference list :     157  ms
QMap::values():      1874  ms 
Java style iterator: 1156  ms 
STL style iterator:  1143  ms

Le style STL et le style Java ont des performances très similaires dans cette référence

22
Fezvez

QMap :: iterator utilise key () et value () - qui se trouvent facilement dans la documentation de Qt 4.8 ou la documentation de Qt-5 .

Modifier:

Une boucle for basée sur la plage génère des codes similaires à celui-ci (voir Référence CPP ):

{
    for (auto __begin = extensions.begin(), __end = extensions.end();
            __begin != __end; ++__begin) {
        auto e = *__begin; // <--- this is QMap::iterator::operator*()
        fout << e.first << "," << e.second << '\n';
    }
} 

QMap :: iterator :: iterator * () est équivalent à QMap :: iterator :: value (), et ne donne pas une paire.

La meilleure façon d'écrire ceci est sans boucle for basée sur la plage:

auto end = extensions.cend();
for (auto it = extensions.cbegin(); it != end; ++it)
{
    std::cout << qPrintable(it.key()) << "," << qPrintable(it.value());
}
15
hmuelner

Dans "l'ancien" C++, en utilisant Qt, vous le feriez comme ceci:

QMap< QString, whatever > extensions;
//...
foreach( QString key, extensions.keys() )
{
    fout << key << "," << extensions.value( key ) << '\n';
}

Je n'ai pas de compilateur C++ 11 ici, mais peut-être que ceci fonctionnera:

for( auto key: extensions.keys() )
{
    fout << key << "," << extensions.value( key ) << '\n';
}

Vous pouvez également utiliser des itérateurs à la place, consultez le lien hmuelners si vous préférez les utiliser.

12
Tim Meyer

J'ai utilisé quelque chose comme ça pour atteindre mon propre résultat. Juste au cas où quelqu'un aurait besoin des clés et des valeurs séparément.

{
   QMap<int,string> map; 

   map.insert(1,"One");
   map.insert(2,"Two");
   map.insert(3,"Three");
   map.insert(4,"Four");   

   fout<<"Values in QMap 'map' are:"<<endl;
   foreach(string str,map)
   {
     cout<<str<<endl;
   };


   fout<<"Keys in QMap 'map' are:"<<endl;
   foreach(int key,map.keys())
   {
     cout<<key<<endl;
   }; 
}  
1
Thierry Joel

Une autre méthode pratique, à partir de QMap Docs . Il permet un accès explicite à la clé et à la valeur (itérateur de style Java):

QMap<QString, QString> extensions;
// ... fill extensions
QMapIterator<QString, QString> i(extensions);
while (i.hasNext()) {
    i.next();
    qDebug() << i.key() << ": " << i.value();
}

Si vous souhaitez pouvoir écraser, utilisez plutôt QMutableMapIterator.

Il existe une autre méthode pratique Qt, si vous ne souhaitez lire que les valeurs, sans les clés (en utilisant Qts foreach et c ++ 11):

QMap<QString, QString> extensions;
// ... fill extensions
foreach (const auto& value, extensions)
{
    // to stuff with value
}
0
DomTomCat