Results 1 to 18 of 18

Thread: Nested QHash in range for loop

  1. #1
    Join Date
    Nov 2015
    Posts
    41
    Thanks
    5
    Thanked 3 Times in 3 Posts
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Nested QHash in range for loop

    Hi,

    It is possible to use this data structure:

    Qt Code:
    1. struct Data{
    2. QString scope;
    3. QString name;
    4. QVariant value;
    5. };
    6.  
    7. QHash<QString, QHash<QString, Data>> data;
    To copy to clipboard, switch view to plain text mode 

    in range for loop like that:

    Qt Code:
    1. for( const Data & d : data )
    2. qDebug()<< d.scope << d.name << d.value;
    To copy to clipboard, switch view to plain text mode 

  2. #2
    Join Date
    Nov 2015
    Posts
    41
    Thanks
    5
    Thanked 3 Times in 3 Posts
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Nested QHash in range for loop

    I know that I must implement begin() and end() inside class which contains these nested QHash.
    But how connect all ranges from inner QHash?
    Last edited by Scope; 3rd December 2015 at 08:48.

  3. #3
    Join Date
    Oct 2009
    Posts
    483
    Thanked 97 Times in 94 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Nested QHash in range for loop

    The nested QHashes must be wrapped in a class for which you need to implement your own iterator and const_iterator. Such an iterator could consist of a triple ((const)iterator on the outer QHash, (const)iterator on the current inner QHash, (c)end() of the current inner QHash).

    Do you really need to go through all this trouble? Consider theses options:
    1. Replace your nested QHash with a flat QHash whose key type is a pair of QStrings.
    2. Keep the nested QHash, and iterate over it with two nested for loops.

    I recommend option 1. Option 2 (and your nested structure in general) only makes sense if you need to collectively manage all bindings sharing the same outer key.

  4. #4
    Join Date
    Nov 2015
    Posts
    41
    Thanks
    5
    Thanked 3 Times in 3 Posts
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Nested QHash in range for loop

    QPair can not be a key in my case.
    Every Data structure is identified by scope ( first QString ) and name (second QString ) and now when i want set some Data i have function:

    Qt Code:
    1. class Container
    2. {
    3. public:
    4. void setData(const Data &data);
    5.  
    6. private:
    7. QHash<QString, QHash<QString, Data>> d;
    8.  
    9. };
    10.  
    11.  
    12. void Container::setData(const Data &data)
    13. {
    14. const QString scope = data.scope;
    15. const QString name = data.name;
    16.  
    17. if( d.contains(scope) ) {
    18. if( d.value(scope).contains(name) )
    19. d[scope][name] = data;
    20. else
    21. d[scope].insert(name, data);
    22. }
    23. else {
    24. d.insert(scope, QHash<QString, Data>{{name, data}});
    25. }
    26. }
    To copy to clipboard, switch view to plain text mode 

    and now I want allow to use this class in range for loop like that

    Qt Code:
    1. Container container;
    2. for(const Data &data : container )
    3. qDebug()<< data.scope << data.name << data.value;
    To copy to clipboard, switch view to plain text mode 

    but if I understand you correctly it will be very difficult.

  5. #5
    Join Date
    Oct 2009
    Posts
    483
    Thanked 97 Times in 94 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Nested QHash in range for loop

    Quote Originally Posted by Scope View Post
    QPair can not be a key in my case.
    Really? What about this:
    Qt Code:
    1. class Container
    2. {
    3. public:
    4. void setData(const Data &data);
    5.  
    6. private:
    7. QHash<QPair<QString, QString>, Data> d;
    8.  
    9. };
    10.  
    11. void Container::setData(const Data &data)
    12. {
    13. d.insert(qMakePair(data.scope, data.name), data);
    14. }
    To copy to clipboard, switch view to plain text mode 

    And here is how you iterate on the container:

    Qt Code:
    1. class Container {
    2. // ...
    3. public:
    4. using const_iterator = QHash<QPair<QString, QString>, Data>::const_iterator;
    5. const_iterator cbegin() const {return d.cbegin();}
    6. const_iterator cend() const {return d.cend();}
    7. const_iterator begin() const {return d.begin();}
    8. const_iterator end() const {return d.end();}
    9. // I omit the non-const iterators on purpose, because they would be more complex than const_iterator, as we have an invariant to maintain on d
    10. // ...
    11. };
    12.  
    13. // ...
    14. Container container;
    15. // ...
    16. for(const Data &data : static_cast<const Container &>(container))
    17. qDebug()<< data.scope << data.name << data.value;
    To copy to clipboard, switch view to plain text mode 

    Disclaimer: I have not tried to compile these snippets.
    Last edited by yeye_olive; 3rd December 2015 at 17:18.

  6. #6
    Join Date
    Nov 2015
    Posts
    41
    Thanks
    5
    Thanked 3 Times in 3 Posts
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Nested QHash in range for loop

    Really? What about this:
    Yes, because in my case, scope( first string ) must be unique and name of data ( second string ) also must be unique in their scope. Then your example with QPair as key has horrible bad performance and complexity when we compare this to nested hash.

  7. #7
    Join Date
    Oct 2009
    Posts
    483
    Thanked 97 Times in 94 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Nested QHash in range for loop

    Quote Originally Posted by Scope View Post
    Yes, because in my case, scope( first string ) must be unique and name of data ( second string ) also must be unique in their scope.
    Which is equivalent to saying that there must be only one Data for each unique (scope, name) combination. Which is why I suggested the pair as key in the first place.
    Quote Originally Posted by Scope View Post
    Then your example with QPair as key has horrible bad performance and complexity when we compare this to nested hash.
    How do you know? Have you run benchmarks? I would like to hear your argument, because I actually believe the opposite to be true in general. You do realize that a hash table has amortized constant average complexity, right? Besides, a flat QHash wastes less memory than nested ones.

    If nesting was so much better, then we would never see things like QHash<QString, Foo>. Instead, people would use crazy structures like QHash<QString, QHash<QString, Foo> > in which you split the original string into two substrings to index into the two levels. But guess what, they don't.

    Seriously though, there is nothing wrong with a hash table indexed by a pair of QStrings. It is about as efficient a structure as you can get with generic containers. As I already pointed out, nesting is useful if you have operations like slicing (e.g. quickly get all bindings for a given scope) or a very specific distribution of (scope, name) pairs. But all we know is that you are interested in iterating over the whole (flat) collection. Other people seem to agree with me:
    http://stackoverflow.com/questions/2...han-nested-map
    http://programmers.stackexchange.com...-combined-keys

    But hey, if you want to waste your time with overly complex data structures with no gain in performance or memory usage (and most likely a loss), be my guest.

  8. #8
    Join Date
    Nov 2015
    Posts
    41
    Thanks
    5
    Thanked 3 Times in 3 Posts
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Nested QHash in range for loop

    How do you know? Have you run benchmarks? I would like to hear your argument
    As you said when we want take all values from specific scope

    Qt Code:
    1. // with QPair as key
    2. QList<Data> Container::data(const QString &scope)
    3. {
    4. QList<Data> data;
    5. for( const QPair<QString, QString> &key : d.keys() )
    6. if( key.first == scope )
    7. data.append( d.value(key));
    8.  
    9. return data;
    10. }
    11.  
    12. // with nested QHash
    13. QList<Data> Container::data(const QString &scope)
    14. {
    15. if( d2.contains(scope) )
    16. return d2.value(scope).values();
    17.  
    18. return QList<Data>();
    19. }
    To copy to clipboard, switch view to plain text mode 

    Maybe first version can be better using std::find_if or std::binary_search but second version is faster.
    But maybe you have right that QPair as key is not worse as I said...

    I am just trying find the best structure, for properties like scope, name and value.
    Main goal is high performance for insert and lookup, and possibility use in range for loop.
    Last edited by Scope; 3rd December 2015 at 20:52.

  9. #9
    Join Date
    Oct 2009
    Posts
    483
    Thanked 97 Times in 94 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Nested QHash in range for loop

    OK, if you need such slicing, then the nested QHashes are the reasonable solution; in that case your setData method would be more efficiently implemented by:
    Qt Code:
    1. void Container::setData(const Data &data) {
    2. d[data.scope].insert(data.name, data);
    3. }
    To copy to clipboard, switch view to plain text mode 
    You could implement a flat iterator by a triple (see my first post).

  10. #10
    Join Date
    Nov 2015
    Posts
    41
    Thanks
    5
    Thanked 3 Times in 3 Posts
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Nested QHash in range for loop

    Thank you very much,
    I found in boost iterator_facade template and example

    Qt Code:
    1. # include "node.hpp"
    2. # include <boost/iterator/iterator_facade.hpp>
    3.  
    4. class node_iterator
    5. : public boost::iterator_facade<
    6. node_iterator
    7. , node_base
    8. , boost::forward_traversal_tag
    9. >
    10. {
    11. public:
    12. node_iterator()
    13. : m_node(0) {}
    14.  
    15. explicit node_iterator(node_base* p)
    16. : m_node(p) {}
    17.  
    18. private:
    19. friend class boost::iterator_core_access;
    20.  
    21. void increment() { m_node = m_node->next(); }
    22.  
    23. bool equal(node_iterator const& other) const
    24. {
    25. return this->m_node == other.m_node;
    26. }
    27.  
    28. node_base& dereference() const { return *m_node; }
    29.  
    30. node_base* m_node;
    31. };
    To copy to clipboard, switch view to plain text mode 

    but i did not know how jump from end of one QHash to begin another?

  11. #11
    Join Date
    Oct 2009
    Posts
    483
    Thanked 97 Times in 94 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Nested QHash in range for loop

    Here you go, a proof of concept in pseudocode of the flat const iterator. It only supports dereferencing and incrementation. It is in fact a quadruple (current and end iterator for outer and inner maps):
    Qt Code:
    1. Data members:
    2. // Iterator on the outer QHash
    3. QHash<QString, QHash<QString, Data> >::const_iterator m_outer;
    4. // End iterator on the outer QHash
    5. QHash<QString, QHash<QString, Data> >::const_iterator m_outerEnd;
    6. // Iterator on the inner QHash. Has an indeterminate value if m_outer == m_outerEnd; otherwise, points to the current binding in the current inner map
    7. QHash<QString, Data>::const_iterator m_inner;
    8. // End iterator on the inner QHash. Has an indeterminate value if m_outer == m_outerEnd; otherwise, is the end() iterator of the current inner map
    9. QHash<QString, Data>::const_iterator m_innerEnd;
    10.  
    11. // Dereferences the iterator; assumes m_outer != m_outerEnd
    12. Dereference:
    13. Return *m_inner
    14.  
    15. // Tests for equality with another iterator on the same container
    16. Equals(const const_iterator &other):
    17. If m_outer == m_outerEnd
    18. Return other.m_outer == other.m_outerEnd
    19. Else
    20. Return m_outer == other.m_outer && m_inner == other.m_inner
    21.  
    22. // A private operation that updates the state right after m_outer has received a new value
    23. NewOuter:
    24. While m_outer != m_outerEnd
    25. m_inner = m_outer->begin()
    26. m_innerEnd = m_outer->end()
    27. If m_inner != m_innerEnd
    28. Break
    29. Increment m_outer
    30.  
    31. // Sets up this iterator at the beginning of the collection c
    32. Begin(const QHash<QString, QHash<QString, Data> > &c):
    33. m_outer = c.begin()
    34. m_outerEnd = c.end()
    35. Call NewOuter
    36.  
    37. // Sets up this iterator at the end of the collection c
    38. End(const QHash<QString, QHash<QString, Data> > &c):
    39. m_outer = m_outerEnd = c.end()
    40.  
    41. // Increments this iterator; assumes m_outer != m_outerEnd
    42. Increment:
    43. Increment m_inner
    44. If m_inner == m_innerEnd
    45. // Done iterating on the current inner map; move to the next outer binding
    46. Increment m_outer
    47. Call NewOuter
    To copy to clipboard, switch view to plain text mode 

  12. The following user says thank you to yeye_olive for this useful post:

    Scope (6th December 2015)

  13. #12
    Join Date
    Jan 2006
    Location
    Graz, Austria
    Posts
    8,416
    Thanks
    37
    Thanked 1,544 Times in 1,494 Posts
    Qt products
    Qt3 Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Nested QHash in range for loop

    And be aware that using a ranged for on a Qt container will require the Qt container to do a deep copy if it is used in more than one variable at the time of iteration.

    Cheers,
    _

  14. #13
    Join Date
    Nov 2015
    Posts
    41
    Thanks
    5
    Thanked 3 Times in 3 Posts
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Nested QHash in range for loop

    Thanks yeye_olive
    Can you look on my implementation? I tested this code and think that it works fine.

    Qt Code:
    1. class Container
    2. {
    3. public:
    4. Container();
    5.  
    6. void setData(const Data &data);
    7.  
    8.  
    9. using value_type = Data;
    10. using inner_range = QHash<QString, value_type>;
    11. using outer_range = QHash<QString, inner_range>;
    12.  
    13. class iterator : std::iterator_traits<inner_range::iterator>
    14. {
    15. public:
    16. using iterator_category = std::forward_iterator_tag;
    17.  
    18. iterator(outer_range::iterator const & outer_iterator,
    19. outer_range::iterator const & outer_end)
    20. : outer_iterator(outer_iterator), outer_end(outer_end)
    21. {
    22. update();
    23. }
    24.  
    25. iterator & operator++()
    26. {
    27. ++inner_iterator;
    28. if(inner_iterator == inner_end)
    29. {
    30. ++outer_iterator;
    31. update();
    32. }
    33. return *this;
    34. }
    35.  
    36. value_type operator*() const
    37. {
    38. return *inner_iterator;
    39. }
    40.  
    41. bool operator==(iterator const & rhs) const
    42. {
    43. bool lhs_end = outer_iterator == outer_end;
    44. bool rhs_end = rhs.outer_iterator == rhs.outer_end;
    45. if(lhs_end && rhs_end)
    46. return true;
    47. if(lhs_end != rhs_end)
    48. return false;
    49. return outer_iterator == rhs.outer_iterator
    50. && inner_iterator == rhs.inner_iterator;
    51. }
    52.  
    53. bool operator!=(iterator const &other) const
    54. {
    55. return !(*this == other);
    56. }
    57.  
    58.  
    59. private:
    60. outer_range::iterator outer_iterator, outer_end;
    61. inner_range::iterator inner_iterator, inner_end;
    62.  
    63. void update()
    64. {
    65. while(outer_iterator != outer_end)
    66. {
    67. inner_iterator = (*outer_iterator).begin();
    68. inner_end = (*outer_iterator).end();
    69. if(inner_iterator == inner_end)
    70. ++outer_iterator;
    71. else
    72. break;
    73. }
    74. }
    75. };
    76.  
    77.  
    78. Container::iterator begin();
    79. Container::iterator end();
    80.  
    81. private:
    82. QHash<QString, QHash<QString, Data>> d;
    83. };
    To copy to clipboard, switch view to plain text mode 

    anda_skoa
    And be aware that using a ranged for on a Qt container will require the Qt container to do a deep copy if it is used in more than one variable at the time of iteration.
    What exactly you mean?
    When I use this container like that:

    Qt Code:
    1. for( const Data &x : d )
    2. qDebug()<< x.scope << "::" << x.name << ", " << x.value;
    To copy to clipboard, switch view to plain text mode 

    then it should not create a copy of each elemenet.

    And importan question, whether i should stay with QHash whether I can obtain better performance using QMap?

    Especially when keys are type of QString which can have between one to over a dozen characters and when each Container should have average 1 <= x <= 30 properties as Data.
    Last edited by Scope; 4th December 2015 at 20:15.

  15. #14
    Join Date
    Jan 2006
    Location
    Graz, Austria
    Posts
    8,416
    Thanks
    37
    Thanked 1,544 Times in 1,494 Posts
    Qt products
    Qt3 Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Nested QHash in range for loop

    Quote Originally Posted by Scope View Post
    What exactly you mean?
    When I use this container like that:

    Qt Code:
    1. for( const Data &x : d )
    2. qDebug()<< x.scope << "::" << x.name << ", " << x.value;
    To copy to clipboard, switch view to plain text mode 

    then it should not create a copy of each elemenet.
    Qt containers are implicitly shared, they use reference counting to avoid deep copying on assignment/copying.

    Qt Code:
    1. QHash<QString, QString> h1;
    2. h1.insert("a", "1");
    3. h1.insert("b", "2");
    4.  
    5. QHash<QString, QString> h2 = h1; // h2 and h1 shared the same data. reference count is 2
    6.  
    7. for (const QString &value, h1) { // h1 detaches, creates a deep copy, h2 and h1 now point to different data, each having a ref count of 1
    8. }
    To copy to clipboard, switch view to plain text mode 
    Detaching is triggered by calling non-const methods. begin()/end() are non-const unless the instance they are called on is const.

    So make sure that either
    - the container is not having a ref count > 1
    - the container is constant
    or use foreach()

    Cheers,
    _

  16. #15
    Join Date
    Nov 2015
    Posts
    41
    Thanks
    5
    Thanked 3 Times in 3 Posts
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Nested QHash in range for loop

    Ok, I think that this should not be a problem - if Qt containers inside range for loop create a deep copy then my class can have a similar behavior.

    And importan question, whether i should stay with QHash whether I can obtain better performance using QMap?

    Especially when keys are type of QString which can have between one to over a dozen characters and when each Container should have average 1 <= x <= 30 properties as Data.
    Thanks,

  17. #16
    Join Date
    Jan 2006
    Location
    Graz, Austria
    Posts
    8,416
    Thanks
    37
    Thanked 1,544 Times in 1,494 Posts
    Qt products
    Qt3 Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Nested QHash in range for loop

    Quote Originally Posted by Scope View Post
    Ok, I think that this should not be a problem - if Qt containers inside range for loop create a deep copy then my class can have a similar behavior.
    Well, usually one doesn't use ranged-for with a Qt container unless it is ensured that there won't be the penalty of the deep copy.

    Cheers,
    _

  18. The following user says thank you to anda_skoa for this useful post:

    Scope (6th December 2015)

  19. #17
    Join Date
    Nov 2015
    Posts
    41
    Thanks
    5
    Thanked 3 Times in 3 Posts
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Nested QHash in range for loop

    I found on Qt blog that this problem can be solve using simple template

    Qt Code:
    1. template<class T> const T &const_(const T &t) { return t; }
    2. for(auto it : const_(vector)) { ... }
    To copy to clipboard, switch view to plain text mode 

    Thanks and regards

  20. #18
    Join Date
    Oct 2009
    Posts
    483
    Thanked 97 Times in 94 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Nested QHash in range for loop

    Quote Originally Posted by Scope View Post
    Can you look on my implementation? I tested this code and think that it works fine.
    Here are my comments on your implementation:

    Your iterator advertizes itself a forward iterator, therefore it should provide default construction and post-incrementation.

    Line 36: the return type should ve value_type &, not value_type (see more about this below).

    Operator ==: your implementation works perfectly, but you could make it faster thanks to the precondition that *this and rhs are on the same container. For instance:
    Qt Code:
    1. return outer_iterator == rhs.outer_iterator && (outer_iterator == outer_end || inner_iterator == rhs.inner_iterator);
    To copy to clipboard, switch view to plain text mode 
    About the wrong return type at line 36: you chose to implement an iterator (as opposed to a const_iterator), which is expected to let the user modify the elements of the container; that is why operator*() should return a non-const reference to the element. Naturally you should also add a comment warning the user against modifying Data::scope and Data::name through this iterator, as this would break the invariant of your Container. (std::map and std::unordered_map have exactly the same problem.)

    You should also provide a const_iterator in addition to the iterator, especially if all the iterations you care about are read-only (and the fact that your current operator*() returns a copy of the element is a clear indication that it is the case). Not only will you enjoy the added safety from the type system, but you will also gain a performance advantage since the underlying QHash::const_iterators will not force the QHash to perform a deep copy in case of implicit sharing.

    Quote Originally Posted by Scope View Post
    And importan question, whether i should stay with QHash whether I can obtain better performance using QMap?

    Especially when keys are type of QString which can have between one to over a dozen characters and when each Container should have average 1 <= x <= 30 properties as Data.
    You will most likely obtain better performance with a QHash than with a QMap. The main advantage of QMap over QHash is that the collection is sorted. Unless you care about that, you should stick with QHash.

    Quote Originally Posted by Scope View Post
    I found on Qt blog that this problem can be solve using simple template

    Qt Code:
    1. template<class T> const T &const_(const T &t) { return t; }
    2. for(auto it : const_(vector)) { ... }
    To copy to clipboard, switch view to plain text mode 
    For that to work, you must provide a const_iterator and const begin() and end() methods. Also, your syntax is friendlier than the static_cast beast I wrote in a previous post, but requires the additional helper definition of const_; I still cannot understand why the standard library does not provide it.

Similar Threads

  1. Loop or Nested Iterator
    By enricong in forum Qt Programming
    Replies: 3
    Last Post: 28th September 2011, 06:06
  2. Replies: 4
    Last Post: 6th August 2011, 01:40
  3. Main loop thread loop communication
    By mcsahin in forum Qt Programming
    Replies: 7
    Last Post: 25th January 2011, 16:31
  4. Replies: 1
    Last Post: 10th February 2009, 09:42
  5. Can QHash::capacity() be smaller than QHash::size()?!?
    By iw2nhl in forum Qt Programming
    Replies: 2
    Last Post: 24th August 2007, 01:17

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Qt is a trademark of The Qt Company.