1

I'm converting a Perl program into Qt/C++. Most code has a straightforward conversion into either C++ or Qt function. However, I'm not sure how to migrate the Perl hash of hashes.

Here's an example of the multilevel hashes that I use to organize some data

$series{$uid}{$studynum}{$seriesnum}{'exportseriesid'} = $exportseriesid;
$series{$uid}{$studynum}{$seriesnum}{'seriesid'} = $seriesid;
$series{$uid}{$studynum}{$seriesnum}{'subjectid'} = $subjectid;
$series{$uid}{$studynum}{$seriesnum}{'studyid'} = $studyid;
$series{$uid}{$studynum}{$seriesnum}{'modality'} = $modality;

I've used the QHash to create single level hashes, such as

QHash<QString, QString> cfg;
int n = cfg["threads"].toInt();

Is there a method similar in C++ or using QHash?

UPDATE:

I ended up using nested QMaps. QMap is automatically sorted by key when iterating over it, while QHash is not. Here is the code I ultimately used

/* create a multilevel hash s[uid][study][series]['attribute'] */
QMap<QString, QMap<int, QMap<int, QMap<QString, QString>>>> s;

/* iterate through the UIDs */
for(QMap<QString, QMap<int, QMap<int, QMap<QString, QString>>>>::iterator a = s.begin(); a != s.end(); ++a) {
    QString uid = a.key();

    /* iterate through the studynums */
    for(QMap<int, QMap<int, QMap<QString, QString>>>::iterator b = s[uid].begin(); b != s[uid].end(); ++b) {
        int studynum = b.key();

        /* iterate through the seriesnums */
        for(QMap<int, QMap<QString, QString>>::iterator c = s[uid][studynum].begin(); c != s[uid][studynum].end(); ++c) {
            int seriesnum = c.key();

            int exportseriesid = s[uid][studynum][seriesnum]["exportseriesid"].toInt();

            /* etc... */
        }
    }
}
Greg B
  • 609
  • 6
  • 19
  • `QHash` is a hash map, which uses a hash function internally to organize data. It is not meant for computing hashes. Look into `QCryptographicHash` for hash functions. I have no clue what the Perl code does, so I have no idea if that is what you wanted. – nwp May 13 '19 at 14:11
  • You could use a `QVariantMap` (or a `QJsonObject`) for that. It will allow you to have variable levels in your structure. – Dimitry Ernot May 13 '19 at 19:04

3 Answers3

3

You can use QHash like that:

QHash<QString, QHash<QString, QString>> two_level_hash;
two_level_hash["first_level"]["second_level"] = "your data";

this works for hashes with level count you want.

Konstantin T.
  • 994
  • 8
  • 22
  • This was what I was looking for, and I ended up using a QMap because the keys are automatically sorted. The keys do not need to be same datatype for all levels, but do need to be the same for all items of each level. So all of level 1 needs to be QString, and all of level 2 integer, etc. The value at the end needs to be the same as well, so I kept it as a QString. I had to figure out how to iterate over the QMaps. I've added to my code above with what I'm using now. – Greg B May 14 '19 at 15:00
2

A direct equivalent of a hash/dictionary is the unordered_map. Then you can nest them, much like in your Perl example. This results in a hierarchy that may be hard to maintain, just like it does in scripting languages when it's pushed too far. The basic idea

#include<iostream>
#include<string>
#include<unordered_map>

using std::string;
using std::cout;
using std::endl;

int main() 
{
    typedef std::unordered_map<string, int>    bottom;
    typedef std::unordered_map<string, bottom> nextlev;
    std::unordered_map<string, nextlev>        h3d;

    h3d["toplev"]["nextlev"]["seriesid"]  = 3;
    h3d["toplev"]["nextlev"]["subjectid"] = 11;

    for (auto k: h3d) {
        cout << k.first << " => " << endl;
        for (auto k2: k.second) {
            cout << "\t" << k2.first << " => " << endl;
            for (auto k3: k2.second)
                cout << "\t\t" << k3.first << " => " << k3.second << endl;
        }
    }   

    return 0;
}

This may (or may not) perform poorly, in some use cases. You probably want a struct to group values. For a far more involved and careful structure see, for example, this post.

Finally, I'd really recommend to implement that multi-level hash as a class. This is a good idea in a scripting language as well, when chained data gets unwieldy: re-write as a class.

zdim
  • 64,580
  • 5
  • 52
  • 81
0

I'm not familiar with perl, but by looks of it I suspect you need something like this:

struct PropertyPath {
    QString uid;
    QString studynum; // or other type of your preference 
    QString seriesnum;// or other type of your preference 
    QString name;
}

uint qHash(const PropertyPath& p, uint seed = 0)
{
    auto h = qHash(p.uid, seed);
    h = qHash(p.studynum, h);
    h = qHash(p.seriesnum, h);
    h = qHash(p.name, h);
    return h;
}

QHash<PropertyPath, QString> cfg;
Marek R
  • 32,568
  • 6
  • 55
  • 140