PDA

View Full Version : QT newbie: QMapIterator not showing any output, but foreach does?



Robert Kulagowski
31st May 2012, 21:55
QT4.8.1

The following is a code snippet. It reads in a text file where each line contains json in a non-pretty-print format.

The intent is to read in each line, extract a key called "prog_id", then store a QMap using prog_id as the key.


QString filename = "/tmp/10021_sched.txt";

QFile inputfile(filename);
if (!inputfile.open(QIODevice::ReadOnly))
{
qDebug() << "Couldn't open filename: " << filename;
return false;
}

QJson::Parser parser;
bool ok;

QMap<QString, QString> map;
QMapIterator<QString, QString> i(map);
QString line;
QString key;

while (!inputfile.atEnd())
{
// Read everything into a QMap first. The largest data file only has around 4000 lines, so not huge.
line = inputfile.readLine();
QVariantMap result = parser.parse(line.toLocal8Bit(), &ok).toMap(); //json parser wants QByteArray
if (!ok)
{
printf("line %d: %s\n", parser.errorLine(), parser.errorString().toUtf8().data());
return false;
}

// Store each line read in from the file using prog_id as the key.
//qDebug() << "progid is " << result["prog_id"];
//qDebug() << "line is " << line;

key = result["prog_id"].toString();

// The next line works.
qDebug() << "key is: " << key;

map[key] = line;
}
inputfile.close();

// Added this because the iterator didn't seem to work.
i.toFront();

// This prints.
printf("Starting loop\n");

while (i.hasNext())
{
// This doesn't print.
printf("Inside loop\n");
i.next();
// I never get any output inside the while
qDebug() << i.key() << ": " << i.value();
}

// This works.
foreach (QString value, map)
{
qDebug() << "value is: " << value;
}

Snippet is obviously kludged together, but I'm not understanding why the while "Inside Loop" never seems to execute.

wysota
31st May 2012, 22:46
What if you initialize "i" in line #42 instead of #14?

Robert Kulagowski
1st June 2012, 03:58
That worked, but why?

I didn't see anything in the online docs stating that the QMapIterator had to be declared after the QMap had been "filled".

d_stranz
1st June 2012, 06:03
The iterator is positioned just before the start of the map as it exists when the iterator is created. If the map is empty, the iterator's hasNext() returns false, because there is nothing to iterate over.

STL-style iterators do not have this restriction. You can declare an STL-style iterator at any time. When you want to use it, you assign or reassign it:



QMap<QString, QString> map;
QMap<QString, QString>::iterator mapIt;
QMap<QString, QString>::iterator mapEIt;

// ... Fill the map

// Now iterate over the contents

mapIt = map.begin();
mapEIt = map.end();
while ( mapIt != mapEIt )
{
// do something
mapIt++;
}

// and then you can reassign the iterators and do it again

mapIt = map.begin();
mapEIt = map.end();
while ( mapIt != mapEIt )
{
// do something else
mapIt++;
}



STL-style iterators (and presumably Java-style iterators) are invalidated as soon as the collection they are iterating over is changed, so you must take care if you are using them to change the collection. The advantage of STL iterators (IMO) are that they can be reused even when the collection changes by simply reassigning them.

I am not sure if the QMapIterator::toFront() method can be used to reset the iterator after the map has changed or not. I don't use them. You could try your original code, but insert a call to i.toFront() at line 47 and see if that works.

Edit: Ah, that last statement is probably incorrect. According to the docs:


If the map is modified while a QMapIterator is active, the QMapIterator will continue iterating over the original map, ignoring the modified copy.

So if you initialize with an empty map, the iterator continues to point to an empty map no matter what.

wysota
1st June 2012, 09:37
That worked, but why?

I didn't see anything in the online docs stating that the QMapIterator had to be declared after the QMap had been "filled".

There is QMapIterator and QMutableMapIterator.

Robert Kulagowski
1st June 2012, 14:36
The iterator is positioned just before the start of the map as it exists when the iterator is created. If the map is empty, the iterator's hasNext() returns false, because there is nothing to iterate over.

<snip>

So if you initialize with an empty map, the iterator continues to point to an empty map no matter what.

That's the secret sauce that I was missing. It makes perfect sense now, but doesn't that mean that the QMapIterator has to be inline with the main body of the code rather than in a header somewhere? (Assuming that I want to continue using java style iterators)

d_stranz
1st June 2012, 15:33
...doesn't that mean that the QMapIterator has to be inline with the main body of the code rather than in a header somewhere?

Yes, and declared at the point of use when the map is complete. Wysota suggests QMutableMapIterator which name implies a different behaviour. An iterator of that type may allow you to declare it in a header.

Iterators are cheap, though - create them on the stack, use them, throw them away. If you're thinking you are optimizing by declaring a member variable, you aren't really.