Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Special Interest Groups
  3. C++ Gurus
  4. Iterator as a member: Tree/Graph-like structure

Iterator as a member: Tree/Graph-like structure

Scheduled Pinned Locked Moved Unsolved C++ Gurus
qlistiteratorsequencegraph
35 Posts 5 Posters 2.3k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • P Pl45m4
    10 Jan 2025, 13:50

    @SimonSchroeder said in Iterator as a member: Tree/Graph-like structure:

    Contrary to what the name suggests it is not a singly (or doubly) linked list. Instead it is a lot more like a vector.

    Yes, I know and there used to be a QLinkedList which is deprecated as of Qt6+...

    Anyway, like @JonB said correctly I have a class, let's call it MyTask which has an int id class member.
    The IDs of MyTask objects are mapped to a custom widget class using a QHash<MyWidget*, int>

    The container (currently I'm still not sure what structure or container will be the best fitting solution) which holds the MyTask items is planned to be iterated when running the program to simulate a Graph/Sequence/Tree-like behavior... similar to PowerPoints "Advanced Animation" / "Animation Trigger" function where one can manage various actions and effects. For example "start with previous", "start after", "begin with..." etc etc...

    In another topic @Christian-Ehrlicher refered to TaskTree, which is kinda what I'm looking for, but instead of some complicated threaded call stack (no need for QFuture, promises, threads, mutex locks etc etc), I thought of some data structure only. I also don't need any threading as my "task" and actions are GUI related so I can't call them from separate threads anyway :)
    I tried many things and looked into some existing "graph" implementations but none of them seem to be suited for my use case unfortunately.
    As you (@SimonSchroeder ) mentioned linked lists, I also tried replacing my MyTask-QObject with a plain C-style linked list (chaining the MyTask struct nodes together using a next pointer)... but this made the process of managing the structure even more complicated.

    The handling when move the active "task" to the next "group" (e.g. everything associated with MyTask::id = 42) is done by me in a top-level "Task Manager" class which is also responsible for managing the list/hashmap/container of MyTask objects and its insertation+deletion...

    Long story short, @SimonSchroeder your idea would work if I had just an integer in my list, but instead I have MyTask * objects, which have an ID beside other things I need... (casual QObject derived class with ID and logic stuff)... so I have to do some look-up or search, assuming that the objects are not sorted by their ID, to find the "next" one, i.e. MyTask obj with next higher ID.
    Gaps should be allowed, so after every action of MyTask::id = 0 is done MyTask::id = 42 should start its work when there is no MyTask::id (1, 2, .... 41)... but this is taken care of by the manager class to find the next valid MyTask obj.

    I'm currently pulling the part out of my main program creating a test case, if anybody is interested :)

    P Offline
    P Offline
    Pl45m4
    wrote on 10 Jan 2025, 14:53 last edited by Pl45m4 1 Oct 2025, 15:41
    #20

    @Pl45m4 said in Iterator as a member: Tree/Graph-like structure:

    I'm currently pulling the part out of my main program creating a test case, if anybody is interested :)

    So here is small program that shows what I'm trying to do. I hope things become clear

    @JonB @Christian-Ehrlicher Thank you guys for your interest :)
    Actually I don't know (I'm not sure) what's the best stucture for this... therefore I tried couple things, including QMap, QHash, QList and plain C-style linked list of nodes...
    When using QHash and I want to iterate straight through, I have to search for "next" ID manually... on the other hand, when using a QMap, first I need to order them by MyTask::id (thanks @Christian-Ehrlicher ) but then the insertations/deletions are quite messy and complicated :)

    QMake project:

    taskSimulation.pro

    QT       += core gui
    
    greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
    
    CONFIG += c++17
    
    # You can make your code fail to compile if it uses deprecated APIs.
    # In order to do so, uncomment the following line.
    #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
    
    SOURCES += \
        main.cpp \
        mainwindow.cpp \
        mytask.cpp \
        taskmanager.cpp
    
    HEADERS += \
        mainwindow.h \
        mytask.h \
        taskmanager.h
    
    # Default rules for deployment.
    qnx: target.path = /tmp/$${TARGET}/bin
    else: unix:!android: target.path = /opt/$${TARGET}/bin
    !isEmpty(target.path): INSTALLS += target
    
    

    mainwindow.h

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    
    class TaskManager;
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        MainWindow(QWidget *parent = nullptr);
        ~MainWindow();
    
    
    
    
    private:
        TaskManager *m_taskMan;
    
    };
    #endif // MAINWINDOW_H
    
    

    mainwindow.cpp

    #include "mainwindow.h"
    #include "taskmanager.h"
    #include <QPushButton>
    #include <QSpinBox>
    #include <QVBoxLayout>
    #include <QFormLayout>
    #include <QScrollArea>
    #include <QHBoxLayout>
    #include <QTextEdit>
    
    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
        , m_taskMan(new TaskManager(this))
    {
    
        QWidget *cntrwdg = new QWidget;
        QVBoxLayout *vbox = new QVBoxLayout;
        QPushButton *btn_add = new QPushButton("Add / Update", this);
        QPushButton *btn_rem = new QPushButton("Delete", this);
        QPushButton *btn_print = new QPushButton("Print", this);
    
        QHBoxLayout *hbox = new QHBoxLayout;
        QPushButton *btn_start = new QPushButton("Start", this);
        QPushButton *btn_stop = new QPushButton("Stop", this);
        hbox->addWidget(btn_start);
        hbox->addWidget(btn_stop);
    
        QSpinBox *spin_seq = new QSpinBox(this);
        spin_seq->setRange(0, 999);
        QSpinBox *spin_seqLen = new QSpinBox(this);
        spin_seqLen->setRange(1, 999);
    
        QTextEdit *textEdit = new QTextEdit(this);
    
        QFormLayout *laySeq = new QFormLayout;
        QFormLayout *layseqLen = new QFormLayout;
    
        laySeq->addRow("Task ID\t\t", spin_seq);
        layseqLen->addRow("Sub-Task Length\t", spin_seqLen);
    
        vbox->addLayout(laySeq);
        vbox->addLayout(layseqLen);
    
        vbox->addWidget(btn_add);
        vbox->addWidget(btn_rem);
        vbox->addWidget(btn_print);
        vbox->addWidget(textEdit);
        vbox->addLayout(hbox);
        cntrwdg->setLayout(vbox);
    
    
        connect(btn_add, &QPushButton::clicked, this, [=](){
            m_taskMan->addTask(spin_seq->value(), spin_seqLen->value());
        });
    
        connect(btn_rem, &QPushButton::clicked, this, [=](){
            m_taskMan->removeTask(spin_seq->value());
        });
    
        connect(btn_print, &QPushButton::clicked, m_taskMan, &TaskManager::print);
    
    
        connect(m_taskMan, &TaskManager::sendToLog, textEdit, &QTextEdit::append);
    
    
        connect(btn_start, &QPushButton::clicked, this, [=](){
            // Assume each sub-task takes 1s to finish
            // just for simulation purpose
            m_taskMan->startSimulation(1000);
        });
    
        connect(btn_stop, &QPushButton::clicked, m_taskMan, &TaskManager::stopSimulation);
    
        connect(m_taskMan, &TaskManager::taskFinished, this, [=](int id){
            qDebug() << "Task" << id << "done.";
        });
    
    
    
        setCentralWidget(cntrwdg);
    
        setGeometry(800, 400, 600, 300);
    
    }
    
    MainWindow::~MainWindow() {}
    
    

    mytask.h

    #ifndef MYTASK_H
    #define MYTASK_H
    
    #include <QObject>
    
    class MyTask : public QObject
    {
        Q_OBJECT
    
    public:
    
        explicit MyTask(int id, int len, QObject *parent = nullptr);
    
        int id() const;
    
        void setLength(int newLength);
        int length() const;
    
    
        // enqueue after seq done (or in between if needed)
        // "resets" counter to "max tasks" = length
        void enqueue();
        // task counter --
        // returns new cnt value
        int cycle();
    
    
    
    private:
    
        int m_id;
        int m_length;
        int m_counter;
    };
    
    #endif // MYTASK_H
    
    

    mytask.cpp

    #include "mytask.h"
    
    #include <QDebug>
    
    MyTask::MyTask(int id, int len, QObject *parent)
        : QObject{parent}
        , m_id{id}
        , m_length{len}
        , m_counter{m_length}
    {
    
    }
    
    void MyTask::setLength(int newLength)
    {
        m_length = newLength;
    }
    
    
    int MyTask::length() const
    {
        return m_length;
    }
    
    void MyTask::enqueue()
    {
        m_counter = m_length;
    }
    
    int MyTask::cycle()
    {
        // do some tasks in a queue
        // ...
        // when all done
        qDebug() << "Tick (Task" << m_id << "SubTasks remaining" << m_counter << ")";
        m_counter--;
    
        if (m_counter == 0) {
            enqueue();
            return 0;
        }
        else {
            return m_counter;
        }
    }
    
    int MyTask::id() const
    {
        return m_id;
    }
    
    

    taskmanager.h

    #ifndef TASKMANAGER_H
    #define TASKMANAGER_H
    
    #include <QObject>
    #include <QList>
    #include <QTimer>
    
    class MyTask;
    
    class TaskManager : public QObject
    {
        Q_OBJECT
    
    public:
    
        explicit TaskManager(QObject *parent = nullptr);
    
        // id: to determine order and associate with buttons
        // len: important for counting down (knowing when task has finished)
        void addTask(int id, int length);
        // remove task with id
        void removeTask(int id);
    
        void nextTask();
    
        void startSimulation(int tick = 1000);
        void stopSimulation();
        void print();
    
    signals:
    
        void sendToLog(QString);
        void taskFinished(int id);
    
    private:
    
        QList<MyTask*>::iterator m_it;
    
        QList<MyTask *> m_taskList;
        QTimer m_timer;
    };
    
    #endif // TASKMANAGER_H
    
    

    taskmanager.cpp

    #include "taskmanager.h"
    #include "mytask.h"
    #include <QDebug>
    
    TaskManager::TaskManager(QObject *parent)
        : QObject{parent}
    {
    
        m_it = m_taskList.begin();
    
        connect(&m_timer, &QTimer::timeout, this, [=](){
    
            // "start" all tasks
            // -> either at the same time
            // -> or (in simulation) one after another
            MyTask &t = *(*m_it);
            if (t.cycle() == 0) {
                emit taskFinished(t.id());
                nextTask();
            }
    
        });
    
    }
    
    void TaskManager::addTask(int id, int length)
    {
        bool found = false;
        QListIterator<MyTask*> i(m_taskList);
        while (i.hasNext()) {
            MyTask *curr = i.next();
            if (curr->id() == id) {
                emit sendToLog("Task exists.");
                found = true;
                if (curr->length() != length) {
                    curr->setLength(length);
                    emit sendToLog(QString("Task %1 updated").arg(id));
                }
                else
                    emit sendToLog("Nothing to do");
            }
    
        }
    
        if (!found) {
            MyTask *t = new MyTask(id, length, this);
            m_taskList.append(t);
            m_it = m_taskList.begin();
            emit sendToLog(QString("Task created: %1 / %2").arg(id).arg(length));
        }
    }
    
    void TaskManager::removeTask(int id)
    {
        QMutableListIterator<MyTask*> i(m_taskList);
        while (i.hasNext()) {
            if (i.next()->id() == id) {
                i.remove();
                emit sendToLog(QString("Task %1 removed").arg(id));
            }
    
        }
    
    }
    
    void TaskManager::nextTask()
    {
        qDebug() << "Task" << "has finished. Starting next...";
    
        if (++m_it == m_taskList.end()) {
            qDebug() << "Reaching end. Restarting...";
            m_it = m_taskList.begin();
        }
    }
    
    
    void TaskManager::startSimulation(int tick)
    {
        m_timer.start(tick);
    }
    
    void TaskManager::stopSimulation()
    {
        m_timer.stop();
    }
    
    void TaskManager::print()
    {
        QString msg = "\n#####\tID ####\tLEN ###############\n";
        QListIterator<MyTask*> i(m_taskList);
        while (i.hasNext()) {
            const MyTask *curr = i.next();
            msg += "Task:\t" + QString::number(curr->id()) + "\t" + QString::number(curr->length()) + "\n";
        }
        msg += "#########################################\n";
    
    
        emit sendToLog(msg);
    }
    
    

    main.cpp

    #include "mainwindow.h"
    
    #include <QApplication>
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        MainWindow w;
        w.show();
        return a.exec();
    }
    
    

    Sorry if I caused any confusion :D
    I not sure how to pull this off properly and in a more efficient way :)
    Thanks again :)

    PS:This is just the "run-through" process... in my main program I also have a QHash<MyWidget *, int> which maps QWidgets to the Task ID... but AFAICS this container/structure is not suited to "run" the loop and start tasks accordingly. It only manages the Widget-TaskID connection.

    PPS:

    How this simulation works:

    Create a couple "Task" with different IDs and lengths (sub-tasks to finish before allowed to move to next task) and press "Start"...
    What I thought of is printed to console :)


    If debugging is the process of removing software bugs, then programming must be the process of putting them in.

    ~E. W. Dijkstra

    J 1 Reply Last reply 10 Jan 2025, 14:56
    0
    • P Pl45m4
      10 Jan 2025, 14:53

      @Pl45m4 said in Iterator as a member: Tree/Graph-like structure:

      I'm currently pulling the part out of my main program creating a test case, if anybody is interested :)

      So here is small program that shows what I'm trying to do. I hope things become clear

      @JonB @Christian-Ehrlicher Thank you guys for your interest :)
      Actually I don't know (I'm not sure) what's the best stucture for this... therefore I tried couple things, including QMap, QHash, QList and plain C-style linked list of nodes...
      When using QHash and I want to iterate straight through, I have to search for "next" ID manually... on the other hand, when using a QMap, first I need to order them by MyTask::id (thanks @Christian-Ehrlicher ) but then the insertations/deletions are quite messy and complicated :)

      QMake project:

      taskSimulation.pro

      QT       += core gui
      
      greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
      
      CONFIG += c++17
      
      # You can make your code fail to compile if it uses deprecated APIs.
      # In order to do so, uncomment the following line.
      #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
      
      SOURCES += \
          main.cpp \
          mainwindow.cpp \
          mytask.cpp \
          taskmanager.cpp
      
      HEADERS += \
          mainwindow.h \
          mytask.h \
          taskmanager.h
      
      # Default rules for deployment.
      qnx: target.path = /tmp/$${TARGET}/bin
      else: unix:!android: target.path = /opt/$${TARGET}/bin
      !isEmpty(target.path): INSTALLS += target
      
      

      mainwindow.h

      #ifndef MAINWINDOW_H
      #define MAINWINDOW_H
      
      #include <QMainWindow>
      
      class TaskManager;
      
      class MainWindow : public QMainWindow
      {
          Q_OBJECT
      
      public:
          MainWindow(QWidget *parent = nullptr);
          ~MainWindow();
      
      
      
      
      private:
          TaskManager *m_taskMan;
      
      };
      #endif // MAINWINDOW_H
      
      

      mainwindow.cpp

      #include "mainwindow.h"
      #include "taskmanager.h"
      #include <QPushButton>
      #include <QSpinBox>
      #include <QVBoxLayout>
      #include <QFormLayout>
      #include <QScrollArea>
      #include <QHBoxLayout>
      #include <QTextEdit>
      
      MainWindow::MainWindow(QWidget *parent)
          : QMainWindow(parent)
          , m_taskMan(new TaskManager(this))
      {
      
          QWidget *cntrwdg = new QWidget;
          QVBoxLayout *vbox = new QVBoxLayout;
          QPushButton *btn_add = new QPushButton("Add / Update", this);
          QPushButton *btn_rem = new QPushButton("Delete", this);
          QPushButton *btn_print = new QPushButton("Print", this);
      
          QHBoxLayout *hbox = new QHBoxLayout;
          QPushButton *btn_start = new QPushButton("Start", this);
          QPushButton *btn_stop = new QPushButton("Stop", this);
          hbox->addWidget(btn_start);
          hbox->addWidget(btn_stop);
      
          QSpinBox *spin_seq = new QSpinBox(this);
          spin_seq->setRange(0, 999);
          QSpinBox *spin_seqLen = new QSpinBox(this);
          spin_seqLen->setRange(1, 999);
      
          QTextEdit *textEdit = new QTextEdit(this);
      
          QFormLayout *laySeq = new QFormLayout;
          QFormLayout *layseqLen = new QFormLayout;
      
          laySeq->addRow("Task ID\t\t", spin_seq);
          layseqLen->addRow("Sub-Task Length\t", spin_seqLen);
      
          vbox->addLayout(laySeq);
          vbox->addLayout(layseqLen);
      
          vbox->addWidget(btn_add);
          vbox->addWidget(btn_rem);
          vbox->addWidget(btn_print);
          vbox->addWidget(textEdit);
          vbox->addLayout(hbox);
          cntrwdg->setLayout(vbox);
      
      
          connect(btn_add, &QPushButton::clicked, this, [=](){
              m_taskMan->addTask(spin_seq->value(), spin_seqLen->value());
          });
      
          connect(btn_rem, &QPushButton::clicked, this, [=](){
              m_taskMan->removeTask(spin_seq->value());
          });
      
          connect(btn_print, &QPushButton::clicked, m_taskMan, &TaskManager::print);
      
      
          connect(m_taskMan, &TaskManager::sendToLog, textEdit, &QTextEdit::append);
      
      
          connect(btn_start, &QPushButton::clicked, this, [=](){
              // Assume each sub-task takes 1s to finish
              // just for simulation purpose
              m_taskMan->startSimulation(1000);
          });
      
          connect(btn_stop, &QPushButton::clicked, m_taskMan, &TaskManager::stopSimulation);
      
          connect(m_taskMan, &TaskManager::taskFinished, this, [=](int id){
              qDebug() << "Task" << id << "done.";
          });
      
      
      
          setCentralWidget(cntrwdg);
      
          setGeometry(800, 400, 600, 300);
      
      }
      
      MainWindow::~MainWindow() {}
      
      

      mytask.h

      #ifndef MYTASK_H
      #define MYTASK_H
      
      #include <QObject>
      
      class MyTask : public QObject
      {
          Q_OBJECT
      
      public:
      
          explicit MyTask(int id, int len, QObject *parent = nullptr);
      
          int id() const;
      
          void setLength(int newLength);
          int length() const;
      
      
          // enqueue after seq done (or in between if needed)
          // "resets" counter to "max tasks" = length
          void enqueue();
          // task counter --
          // returns new cnt value
          int cycle();
      
      
      
      private:
      
          int m_id;
          int m_length;
          int m_counter;
      };
      
      #endif // MYTASK_H
      
      

      mytask.cpp

      #include "mytask.h"
      
      #include <QDebug>
      
      MyTask::MyTask(int id, int len, QObject *parent)
          : QObject{parent}
          , m_id{id}
          , m_length{len}
          , m_counter{m_length}
      {
      
      }
      
      void MyTask::setLength(int newLength)
      {
          m_length = newLength;
      }
      
      
      int MyTask::length() const
      {
          return m_length;
      }
      
      void MyTask::enqueue()
      {
          m_counter = m_length;
      }
      
      int MyTask::cycle()
      {
          // do some tasks in a queue
          // ...
          // when all done
          qDebug() << "Tick (Task" << m_id << "SubTasks remaining" << m_counter << ")";
          m_counter--;
      
          if (m_counter == 0) {
              enqueue();
              return 0;
          }
          else {
              return m_counter;
          }
      }
      
      int MyTask::id() const
      {
          return m_id;
      }
      
      

      taskmanager.h

      #ifndef TASKMANAGER_H
      #define TASKMANAGER_H
      
      #include <QObject>
      #include <QList>
      #include <QTimer>
      
      class MyTask;
      
      class TaskManager : public QObject
      {
          Q_OBJECT
      
      public:
      
          explicit TaskManager(QObject *parent = nullptr);
      
          // id: to determine order and associate with buttons
          // len: important for counting down (knowing when task has finished)
          void addTask(int id, int length);
          // remove task with id
          void removeTask(int id);
      
          void nextTask();
      
          void startSimulation(int tick = 1000);
          void stopSimulation();
          void print();
      
      signals:
      
          void sendToLog(QString);
          void taskFinished(int id);
      
      private:
      
          QList<MyTask*>::iterator m_it;
      
          QList<MyTask *> m_taskList;
          QTimer m_timer;
      };
      
      #endif // TASKMANAGER_H
      
      

      taskmanager.cpp

      #include "taskmanager.h"
      #include "mytask.h"
      #include <QDebug>
      
      TaskManager::TaskManager(QObject *parent)
          : QObject{parent}
      {
      
          m_it = m_taskList.begin();
      
          connect(&m_timer, &QTimer::timeout, this, [=](){
      
              // "start" all tasks
              // -> either at the same time
              // -> or (in simulation) one after another
              MyTask &t = *(*m_it);
              if (t.cycle() == 0) {
                  emit taskFinished(t.id());
                  nextTask();
              }
      
          });
      
      }
      
      void TaskManager::addTask(int id, int length)
      {
          bool found = false;
          QListIterator<MyTask*> i(m_taskList);
          while (i.hasNext()) {
              MyTask *curr = i.next();
              if (curr->id() == id) {
                  emit sendToLog("Task exists.");
                  found = true;
                  if (curr->length() != length) {
                      curr->setLength(length);
                      emit sendToLog(QString("Task %1 updated").arg(id));
                  }
                  else
                      emit sendToLog("Nothing to do");
              }
      
          }
      
          if (!found) {
              MyTask *t = new MyTask(id, length, this);
              m_taskList.append(t);
              m_it = m_taskList.begin();
              emit sendToLog(QString("Task created: %1 / %2").arg(id).arg(length));
          }
      }
      
      void TaskManager::removeTask(int id)
      {
          QMutableListIterator<MyTask*> i(m_taskList);
          while (i.hasNext()) {
              if (i.next()->id() == id) {
                  i.remove();
                  emit sendToLog(QString("Task %1 removed").arg(id));
              }
      
          }
      
      }
      
      void TaskManager::nextTask()
      {
          qDebug() << "Task" << "has finished. Starting next...";
      
          if (++m_it == m_taskList.end()) {
              qDebug() << "Reaching end. Restarting...";
              m_it = m_taskList.begin();
          }
      }
      
      
      void TaskManager::startSimulation(int tick)
      {
          m_timer.start(tick);
      }
      
      void TaskManager::stopSimulation()
      {
          m_timer.stop();
      }
      
      void TaskManager::print()
      {
          QString msg = "\n#####\tID ####\tLEN ###############\n";
          QListIterator<MyTask*> i(m_taskList);
          while (i.hasNext()) {
              const MyTask *curr = i.next();
              msg += "Task:\t" + QString::number(curr->id()) + "\t" + QString::number(curr->length()) + "\n";
          }
          msg += "#########################################\n";
      
      
          emit sendToLog(msg);
      }
      
      

      main.cpp

      #include "mainwindow.h"
      
      #include <QApplication>
      
      int main(int argc, char *argv[])
      {
          QApplication a(argc, argv);
          MainWindow w;
          w.show();
          return a.exec();
      }
      
      

      Sorry if I caused any confusion :D
      I not sure how to pull this off properly and in a more efficient way :)
      Thanks again :)

      PS:This is just the "run-through" process... in my main program I also have a QHash<MyWidget *, int> which maps QWidgets to the Task ID... but AFAICS this container/structure is not suited to "run" the loop and start tasks accordingly. It only manages the Widget-TaskID connection.

      PPS:

      How this simulation works:

      Create a couple "Task" with different IDs and lengths (sub-tasks to finish before allowed to move to next task) and press "Start"...
      What I thought of is printed to console :)

      J Offline
      J Offline
      JonB
      wrote on 10 Jan 2025, 14:56 last edited by
      #21

      @Pl45m4 said in Iterator as a member: Tree/Graph-like structure:

      When using QHash and I want to iterate straight through, I have to search for "next" ID manually...

      Yes that's exactly as @Christian-Ehrlicher said, because QHash is not ordered. Forget about it!

      on the other hand, when using a QMap, first I need to order them by MyTask::id (thanks @Christian-Ehrlicher ) but then the insertations/deletions are quite messy and complicated :)

      Why are insertions/deletions a problem?? (I haven't looked at your code!.)

      P 1 Reply Last reply 10 Jan 2025, 15:19
      0
      • J JonB
        10 Jan 2025, 14:56

        @Pl45m4 said in Iterator as a member: Tree/Graph-like structure:

        When using QHash and I want to iterate straight through, I have to search for "next" ID manually...

        Yes that's exactly as @Christian-Ehrlicher said, because QHash is not ordered. Forget about it!

        on the other hand, when using a QMap, first I need to order them by MyTask::id (thanks @Christian-Ehrlicher ) but then the insertations/deletions are quite messy and complicated :)

        Why are insertions/deletions a problem?? (I haven't looked at your code!.)

        P Offline
        P Offline
        Pl45m4
        wrote on 10 Jan 2025, 15:19 last edited by
        #22

        @JonB said in Iterator as a member: Tree/Graph-like structure:

        Why are insertions/deletions a problem?? (I haven't looked at your code!.)

        Because I still have to "search" for the next ID in order (as it's not always lastID + 1) that would make this more like being close to O(n^2) than O(n) or even O(1), right? Or am I missing something?!

        Also I asked initially if it's "ok" to have a global (member) iterator around instead of keeping some index (which might change) or a direct pointer to the item itself (= what also is stored in the container).


        If debugging is the process of removing software bugs, then programming must be the process of putting them in.

        ~E. W. Dijkstra

        J 1 Reply Last reply 10 Jan 2025, 15:46
        0
        • P Pl45m4
          10 Jan 2025, 15:19

          @JonB said in Iterator as a member: Tree/Graph-like structure:

          Why are insertions/deletions a problem?? (I haven't looked at your code!.)

          Because I still have to "search" for the next ID in order (as it's not always lastID + 1) that would make this more like being close to O(n^2) than O(n) or even O(1), right? Or am I missing something?!

          Also I asked initially if it's "ok" to have a global (member) iterator around instead of keeping some index (which might change) or a direct pointer to the item itself (= what also is stored in the container).

          J Offline
          J Offline
          JonB
          wrote on 10 Jan 2025, 15:46 last edited by JonB 1 Oct 2025, 15:52
          #23

          @Pl45m4 said in Iterator as a member: Tree/Graph-like structure:

          Because I still have to "search" for the next ID in order (as it's not always lastID + 1) that would make this more like being close to O(n^2) than O(n) or even O(1), right? Or am I missing something?!

          I don't understand this at all. I don't even know whether you mean doing this at insertion, deletion or search-for-next time, but in all cases my searches will be O(log(n)). And that applies when looking for key 10 whether it finds it or returns where it ought to be if it does not exist.

          Since @Christian-Ehrlicher said I may not have made it 100% clear. All in all I would probably use

          QMap<int, Task *> map;
          map.insert(task->id, task);
          

          You can use QMap's lower/upperBound() to find where you got to/where to start from next, and this will work even if the previously noted id number no longer exists (e.g. it has been deleted). [Go read docs about what these return if the key you ask for does not exist, you do not have to explicitly find last + 1 in existence, this is the bit you are not understanding.] And you can assume that will be O(log(n)) because it knows the key search is ordered, unless it is brain-damaged, which I imagine it is not :) Which is all similar for QMap as it would be if you wrote your own ordered vector for binary search or red-black tree. (I am guessing QMap is some kind of red-black tree?)

          Also I asked initially if it's "ok" to have a global (member) iterator around instead of keeping some index (which might change) or a direct pointer to the item itself (= what also is stored in the container).

          I think this was covered in @Christian-Ehrlicher's initial answer, where an iterator is no better than a pointer to keep around in the case where that item may have been deleted.

          P 1 Reply Last reply 10 Jan 2025, 16:13
          1
          • J JonB
            10 Jan 2025, 15:46

            @Pl45m4 said in Iterator as a member: Tree/Graph-like structure:

            Because I still have to "search" for the next ID in order (as it's not always lastID + 1) that would make this more like being close to O(n^2) than O(n) or even O(1), right? Or am I missing something?!

            I don't understand this at all. I don't even know whether you mean doing this at insertion, deletion or search-for-next time, but in all cases my searches will be O(log(n)). And that applies when looking for key 10 whether it finds it or returns where it ought to be if it does not exist.

            Since @Christian-Ehrlicher said I may not have made it 100% clear. All in all I would probably use

            QMap<int, Task *> map;
            map.insert(task->id, task);
            

            You can use QMap's lower/upperBound() to find where you got to/where to start from next, and this will work even if the previously noted id number no longer exists (e.g. it has been deleted). [Go read docs about what these return if the key you ask for does not exist, you do not have to explicitly find last + 1 in existence, this is the bit you are not understanding.] And you can assume that will be O(log(n)) because it knows the key search is ordered, unless it is brain-damaged, which I imagine it is not :) Which is all similar for QMap as it would be if you wrote your own ordered vector for binary search or red-black tree. (I am guessing QMap is some kind of red-black tree?)

            Also I asked initially if it's "ok" to have a global (member) iterator around instead of keeping some index (which might change) or a direct pointer to the item itself (= what also is stored in the container).

            I think this was covered in @Christian-Ehrlicher's initial answer, where an iterator is no better than a pointer to keep around in the case where that item may have been deleted.

            P Offline
            P Offline
            Pl45m4
            wrote on 10 Jan 2025, 16:13 last edited by Pl45m4 1 Oct 2025, 16:16
            #24

            @JonB said in Iterator as a member: Tree/Graph-like structure:

            Since @Christian-Ehrlicher said I may not have made it 100% clear. All in all I would probably use

            QMap<int, Task *> map;
            map.insert(task->id, task);
            

            This makes totally sense and I also got what @Christian-Ehrlicher wrote above about a custom "key", but is it really a good idea to map MyTask to members of itself?!
            Is there no other magical way to do it?
            That stopped me from going over this approach in my head any further.
            Because the "fun" thing is that (with a QMap) I currently wouldn't know what to put as value otherwise, when using a key like @Christian-Ehrlicher described before and correctly :))

            I have

            // my current approach task container
            QList<MyTask*> taskList;
            
            // (not included in my example and not relevant for looping the tasks)
            // where "int" equals a valid MyTask::id
            QHash<MyWidget *, int> taskWidgetMap;
            

            so the information what Task has which ID is already stored in MyTask class

            To get somewhere with this, I will try @Christian-Ehrlicher 's approach and your MyTask <--> MyTask::id mapping now and see how it integrates into the rest. ;-)

            Btw: Now I've read through QSet<T> more carefully and figured out that my initial thought (in my head without specifying any data structure) was about something like an ordered (ideally hash-based) one-dimensional structure (= "list", no key-value dict).... which does not existing in this form :)

            So yeah, I will report back later ;-)

            Besides the data struture mess, have you tried my example @JonB @Christian-Ehrlicher ? What do you think? :)

            Highly appreciate all your input and the discussion here :)


            If debugging is the process of removing software bugs, then programming must be the process of putting them in.

            ~E. W. Dijkstra

            J 1 Reply Last reply 10 Jan 2025, 16:32
            0
            • P Pl45m4
              10 Jan 2025, 16:13

              @JonB said in Iterator as a member: Tree/Graph-like structure:

              Since @Christian-Ehrlicher said I may not have made it 100% clear. All in all I would probably use

              QMap<int, Task *> map;
              map.insert(task->id, task);
              

              This makes totally sense and I also got what @Christian-Ehrlicher wrote above about a custom "key", but is it really a good idea to map MyTask to members of itself?!
              Is there no other magical way to do it?
              That stopped me from going over this approach in my head any further.
              Because the "fun" thing is that (with a QMap) I currently wouldn't know what to put as value otherwise, when using a key like @Christian-Ehrlicher described before and correctly :))

              I have

              // my current approach task container
              QList<MyTask*> taskList;
              
              // (not included in my example and not relevant for looping the tasks)
              // where "int" equals a valid MyTask::id
              QHash<MyWidget *, int> taskWidgetMap;
              

              so the information what Task has which ID is already stored in MyTask class

              To get somewhere with this, I will try @Christian-Ehrlicher 's approach and your MyTask <--> MyTask::id mapping now and see how it integrates into the rest. ;-)

              Btw: Now I've read through QSet<T> more carefully and figured out that my initial thought (in my head without specifying any data structure) was about something like an ordered (ideally hash-based) one-dimensional structure (= "list", no key-value dict).... which does not existing in this form :)

              So yeah, I will report back later ;-)

              Besides the data struture mess, have you tried my example @JonB @Christian-Ehrlicher ? What do you think? :)

              Highly appreciate all your input and the discussion here :)

              J Offline
              J Offline
              JonB
              wrote on 10 Jan 2025, 16:32 last edited by
              #25

              @Pl45m4

              • Christian's approach of defining a < operator for the Task struct itself is required if and only if you wish to have a Task * as the key for the QMap. Which is what he says you had stated initially.
              • But I don't see why you would want or need that (my Task * is the value, not the key). I just use an int as the key and pass task->id for that at map insert time.

              Up to you.

              P 1 Reply Last reply 10 Jan 2025, 16:39
              0
              • J JonB
                10 Jan 2025, 16:32

                @Pl45m4

                • Christian's approach of defining a < operator for the Task struct itself is required if and only if you wish to have a Task * as the key for the QMap. Which is what he says you had stated initially.
                • But I don't see why you would want or need that (my Task * is the value, not the key). I just use an int as the key and pass task->id for that at map insert time.

                Up to you.

                P Offline
                P Offline
                Pl45m4
                wrote on 10 Jan 2025, 16:39 last edited by
                #26

                @JonB said in Iterator as a member: Tree/Graph-like structure:

                I just use an int as the key and pass task->id for that at map insert time

                Yeah that makes sense... but my concern is/was that I have some redundancy. MyTask::id = key AND also already stored in MyTask (and accessible via something like value().id)...

                Will try it out.


                If debugging is the process of removing software bugs, then programming must be the process of putting them in.

                ~E. W. Dijkstra

                J 1 Reply Last reply 10 Jan 2025, 16:52
                0
                • P Pl45m4
                  10 Jan 2025, 16:39

                  @JonB said in Iterator as a member: Tree/Graph-like structure:

                  I just use an int as the key and pass task->id for that at map insert time

                  Yeah that makes sense... but my concern is/was that I have some redundancy. MyTask::id = key AND also already stored in MyTask (and accessible via something like value().id)...

                  Will try it out.

                  J Offline
                  J Offline
                  JonB
                  wrote on 10 Jan 2025, 16:52 last edited by JonB 1 Oct 2025, 16:52
                  #27

                  @Pl45m4
                  You are saving/losing an int for the key. But even another way to use a QMap you have to provide some key to go with a value. If you use Task * as the key that costs a pointer (even if you also store Task * as the value too) which is actually bigger than an int. Plus your QMap actually goes wrong if you go change what the key pointer points to, or the id inside that, if you change the id in the Task your QMap won't rearrange itself!

                  Many times we do key-value pairs like this. I could be wrong, but when, say, you have a database table with a primary (or unique) key/index I don't think that stores a "pointer to" its value somewhere in the row for its data, I think it copies the value to the index and then keeps that in sync if the row changes.

                  But if you are happier with the key as a Task * and override the < operator that is fine too.

                  1 Reply Last reply
                  1
                  • C Christian Ehrlicher
                    10 Jan 2025, 14:06

                    Sorry but I don't understand what you mean - simply replace QMap<MyTask *, whatever> with QMap<Key, whatever> and provide a operator<() for the key (I was wrong above - you don't have to provide a qHash() but a operator <() for a QMap)

                    struct Key {
                      MyTask *task;
                      bool operator <(const Key &o) const
                      {
                        return  task->id < o.task->id;
                      }
                    };
                    
                     QMap<Key, something> myMap;
                    
                    P Offline
                    P Offline
                    Pl45m4
                    wrote on 10 Jan 2025, 17:50 last edited by
                    #28

                    @Christian-Ehrlicher said in Iterator as a member: Tree/Graph-like structure:

                    simply replace QMap<MyTask *, whatever> with QMap<Key, whatever> and provide a operator<() for the key (I was wrong above - you don't have to provide a qHash() but a operator <() for a QMap)

                    struct Key {
                      MyTask *task;
                      bool operator <(const Key &o) const
                      {
                        return  task->id < o.task->id;
                      }
                    };
                    
                     QMap<Key, something> myMap;
                    

                    One more thing @Christian-Ehrlicher :
                    Is there a reason why you picked an extra struct for the Key instead of using the MyTask::operator < directly?


                    If debugging is the process of removing software bugs, then programming must be the process of putting them in.

                    ~E. W. Dijkstra

                    C 1 Reply Last reply 10 Jan 2025, 17:52
                    0
                    • P Pl45m4
                      10 Jan 2025, 17:50

                      @Christian-Ehrlicher said in Iterator as a member: Tree/Graph-like structure:

                      simply replace QMap<MyTask *, whatever> with QMap<Key, whatever> and provide a operator<() for the key (I was wrong above - you don't have to provide a qHash() but a operator <() for a QMap)

                      struct Key {
                        MyTask *task;
                        bool operator <(const Key &o) const
                        {
                          return  task->id < o.task->id;
                        }
                      };
                      
                       QMap<Key, something> myMap;
                      

                      One more thing @Christian-Ehrlicher :
                      Is there a reason why you picked an extra struct for the Key instead of using the MyTask::operator < directly?

                      C Online
                      C Online
                      Christian Ehrlicher
                      Lifetime Qt Champion
                      wrote on 10 Jan 2025, 17:52 last edited by
                      #29

                      @Pl45m4 said in Iterator as a member: Tree/Graph-like structure:

                      instead of using the MyTask::operator < directly?

                      Because this operator would not be used by a QMap<MyTask*, ...> as you store a pointer, not a value.

                      Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
                      Visit the Qt Academy at https://academy.qt.io/catalog

                      P 1 Reply Last reply 10 Jan 2025, 17:55
                      2
                      • C Christian Ehrlicher
                        10 Jan 2025, 17:52

                        @Pl45m4 said in Iterator as a member: Tree/Graph-like structure:

                        instead of using the MyTask::operator < directly?

                        Because this operator would not be used by a QMap<MyTask*, ...> as you store a pointer, not a value.

                        P Offline
                        P Offline
                        Pl45m4
                        wrote on 10 Jan 2025, 17:55 last edited by
                        #30

                        @Christian-Ehrlicher

                        Ah I see, thanks.


                        If debugging is the process of removing software bugs, then programming must be the process of putting them in.

                        ~E. W. Dijkstra

                        1 Reply Last reply
                        0
                        • G Offline
                          G Offline
                          GrecKo
                          Qt Champions 2018
                          wrote on 10 Jan 2025, 22:00 last edited by
                          #31

                          How many elements would there be in this container?

                          J P 2 Replies Last reply 10 Jan 2025, 22:25
                          1
                          • G GrecKo
                            10 Jan 2025, 22:00

                            How many elements would there be in this container?

                            J Offline
                            J Offline
                            JonB
                            wrote on 10 Jan 2025, 22:25 last edited by
                            #32

                            @GrecKo
                            :) Unless it's like more than 100, and this is called many times, and the tasks don't take long to run comparatively, I suspect the whole "fast lookup" won't matter much! But maybe still the principle of how you pick up where you left off from.

                            1 Reply Last reply
                            1
                            • G GrecKo
                              10 Jan 2025, 22:00

                              How many elements would there be in this container?

                              P Offline
                              P Offline
                              Pl45m4
                              wrote on 10 Jan 2025, 23:18 last edited by
                              #33

                              @GrecKo

                              Probably 100-200 at max... usually around 50, I would say :)

                              Sure I could use some inefficient for loop and search/compare each value manually, but a more efficient and cleaner way seems more reasonable to me ;)


                              If debugging is the process of removing software bugs, then programming must be the process of putting them in.

                              ~E. W. Dijkstra

                              1 Reply Last reply
                              1
                              • G Offline
                                G Offline
                                GrecKo
                                Qt Champions 2018
                                wrote on 11 Jan 2025, 00:09 last edited by
                                #34

                                An "inefficient" simple for loop may very well be the most efficient after all. When you have 50 elements it won't matter much anyway, pick the container with the friendliest API depending on how you plan to access it.

                                P 1 Reply Last reply 11 Jan 2025, 00:40
                                1
                                • G GrecKo
                                  11 Jan 2025, 00:09

                                  An "inefficient" simple for loop may very well be the most efficient after all. When you have 50 elements it won't matter much anyway, pick the container with the friendliest API depending on how you plan to access it.

                                  P Offline
                                  P Offline
                                  Pl45m4
                                  wrote on 11 Jan 2025, 00:40 last edited by
                                  #35

                                  @GrecKo said in Iterator as a member: Tree/Graph-like structure:

                                  pick the container with the friendliest API depending on how you plan to access it.

                                  Still what @Christian-Ehrlicher and @JonB suggested doesn't sound too bad :)
                                  Will definitely try it.

                                  Similar question: Why is QButtonGroup using a QHash-map for its member buttons? :))
                                  I doubt that there ever will be thousands of button widgets in one group :)


                                  If debugging is the process of removing software bugs, then programming must be the process of putting them in.

                                  ~E. W. Dijkstra

                                  1 Reply Last reply
                                  1

                                  29/35

                                  10 Jan 2025, 17:52

                                  • Login

                                  • Login or register to search.
                                  29 out of 35
                                  • First post
                                    29/35
                                    Last post
                                  0
                                  • Categories
                                  • Recent
                                  • Tags
                                  • Popular
                                  • Users
                                  • Groups
                                  • Search
                                  • Get Qt Extensions
                                  • Unsolved