Цикл по всем модельным индексам и их дочерним элементам вызывает ошибку переполнения стека

Я создал следующие функции, чтобы перебрать все индексы в таблице или дереве и найти тот, который содержит требуемую строку:

#include <QModelIndex>
#include <QAbstractItemModel>
#include <QAbstractItemView>

QModelIndex findIndexByString(const QAbstractItemModel* model, const QString& text,  const QModelIndex& parent = QModelIndex());
QModelIndex findIndexByString(const QModelIndex& index, const QString& text) {
// First check this particular index
if(index.data().toString() == text)
return index;
// Check if it has children and loop
const QAbstractItemModel* model = index.model();
if(model==nullptr)
return QModelIndex();
if (!model->hasChildren(index))
return QModelIndex();
// STACK OVERFLOW HERE (model and index keep repeating)
return findIndexByString(model, text, index);
}
QModelIndex findIndexByString(const QAbstractItemModel* model, const QString& text,  const QModelIndex& parent) {
int rows = model->rowCount(parent);
int cols = model->columnCount(parent);
for (int i = 0; i < rows; ++i)
for (int j = 0; j < cols; ++j) {
QModelIndex index_child = findIndexByString(model->index(i, j, parent), text);
if(index_child.isValid())
return index_child;
}
return QModelIndex();
}
QModelIndex findIndexByString(const QAbstractItemView* view, const QString& text) {
//const QModelIndex root = view->rootIndex();
const QAbstractItemModel* model = view->model();
return findIndexByString(model, text);
}

Этот код самодостаточен и не имеет никаких зависимостей вне библиотеки Qt.

Я столкнулся с ошибкой переполнения стека при зацикливании некоторой модели во внешнем коде.

Я пытаюсь создать общий правильный алгоритм для этого. Правильный ли алгоритм выше?

2

Решение

Код выглядит хорошо. Это классический поиск в глубину. Он выходит за пределы стека, потому что ваша модель не дерево, а циклический граф и / или это имеет бесконечную глубину.

Мы можем переформулировать поиск с помощью явного стека и добавить тесты, обеспечивающие разумное поведение перед лицом некорректно сформированной модели.

Если ваша модель имеет бесконечную глубину или циклы по дизайну, приведенный ниже код будет действовать надлежащим образом и не выйдет за пределы стека.

// https://github.com/KubaO/stackoverflown/tree/master/questions/model-find-diagnostic-44416637
#include <QtGui>

Во-первых, нам понадобится набор индексов, так как QSet будет работать только с постоянными индексами, которые мы не используем:

class IndexSet : public QMap<QModelIndex, bool> {
public:
void insert(const QModelIndex & key) { QMap::insert(key, true); }
};

Состояние искателя, которое мы будем хранить в явном стеке:

struct FindState {
QModelIndex index;
int rows;
int cols;
int i = 0;
int j = 0;
FindState() = default;
FindState(const QAbstractItemModel* model, const QModelIndex & index) :
index(index),
rows(model->rowCount(index)),
cols(model->columnCount(index)) {}
bool isInitial() const { return i == 0 && j == 0; }
bool equalTo(const QVariant & value) const {
return index.isValid() && index.data() == value;
}
bool operator==(const FindState & o) const {
return index == o.index;
}
};

QDebug operator<<(QDebug dbg, const FindState & s) {
return dbg << "{" << s.index << "," << s.i << "," << s.j << "}";
}

Затем утилита поиска, которая проверяет, что все в порядке, и что модель не ведет себя неправильно:

QModelIndex findIndexByValue(const QAbstractItemModel* model, const QVariant& needle,
const QModelIndex& parent = {}) {
int iterations = {};
int maxDepth = {};
const auto depthLimit = 100;
IndexSet indexes;
QStack<FindState> states;
states.push({model, parent});
for (; !states.isEmpty(); iterations++) {
auto valid = true;
auto & top = states.top();
if (top.isInitial()) {
if (states.size() > 1) {
if (!top.index.isValid()) {
qWarning() << "the index isn't valid, stack:" << states;
valid = false;
}
if (!model->hasIndex(top.index.row(), top.index.column(), top.index.parent())) {
qWarning() << "the index doesn't exist, stack:" << states;
valid = false;
}
}
if (indexes.contains(top.index)) {
qWarning() << "skipping already seen index" << top.index;
valid = false;
}
if (valid) {
indexes.insert(top.index);
if (top.equalTo(needle))
break;
}
}
if (valid && model->hasChildren(top.index) && top.i < top.rows && top.j < top.cols) {
FindState state(model, model->index(top.i, top.j, top.index));
top.i ++;
if (top.i == top.rows) {
top.i = 0;
top.j ++;
}
if (states.contains(state))
qWarning() << "skipping duplicate index" << state.index;
else if (states.size() == depthLimit)
qWarning() << "stack is full, skipping index" << state.index;
else {
states.push(state);
maxDepth = std::max(maxDepth, states.size());
}
} else
states.pop();
}
qDebug() << "max depth" << maxDepth << "iterations" << iterations;
return states.isEmpty() ? QModelIndex() : states.top().index;
}

QModelIndex findIndexByString(const QAbstractItemModel* model, const QString& needle, const QModelIndex& parent = {}) {
return findIndexByValue(model, QVariant::fromValue(needle), parent);
}

Для сравнения мы можем включить исходные реализации из вопроса:

QModelIndex findIndexByString2(const QAbstractItemModel* model, const QString& text,  const QModelIndex& parent = QModelIndex());
QModelIndex findIndexByString2(const QModelIndex& index, const QString& text) {
if (index.data().toString() == text)
return index;
auto model = index.model();
if (!model || !model->hasChildren(index))
return {};
return findIndexByString2(model, text, index);
}

QModelIndex findIndexByString2(const QAbstractItemModel* model, const QString& text,  const QModelIndex& parent) {
int rows = model->rowCount(parent);
int cols = model->columnCount(parent);
for (int i = 0; i < rows; ++i)
for (int j = 0; j < cols; ++j) {
auto child = findIndexByString2(model->index(i, j, parent), text);
if (child.isValid())
return child;
}
return {};
}

Наконец, нам нужен элементарный тестовый комплект. Путь в модель представлен вектором позиций внутри элемента. Каждая позиция имеет строковое представление.

struct Pos {
int row, col;
QString toString() const { return QStringLiteral("(%1,%2)").arg(row).arg(col); }
};
Q_DECLARE_TYPEINFO(Pos, Q_PRIMITIVE_TYPE);
using Path = QVector<Pos>;

Строитель добавляет элементы в модель, чтобы убедиться, что данный путь является допустимым. Значением каждого элемента является строка, представляющая его путь в виде объединения позиций, составляющих путь (row,col)(row,col)..., Когда построитель строит модель, он также сохраняет пути всех созданных им элементов.

struct Builder {
QStandardItemModel * model;
QStringList paths;
Path add(Path path, const Pos & pos) {
auto item = model->invisibleRootItem();
path.push_back(pos);
QString pathString;
for (auto p : path) {
pathString += p.toString();
auto child = item->child(p.row, p.col);
if (!child) {
item->setChild(p.row, p.col, child = new QStandardItem(pathString));
paths << pathString;
}
item = child;
}
return path;
}
explicit Builder(QStandardItemModel * model) : model(model) {}
};

Наконец, мы можем создать модель и убедиться, что оба метода поиска элементов дают одинаковые результаты:

int main() {
QStandardItemModel model;
Builder b(&model);
Path path, ____;
path = b.add({}, {1, 0});     // *(1,0)
____ = b.add(path, {1, 1});   //  (1,0)-(1,1)
____ = b.add(path, {0, 0});   //  (1,0)-(0,0)
path = b.add({}, {1, 1});     // *(1,1)
path = b.add(path, {3, 3});   // *(1,1)-(3,3)
____ = b.add(path, {0, 0});   //  (1,1)-(3,3)-(0,0)
path.pop_back();              // -(1,1)
path = b.add(path, {2, 2});   // *(1,1)-(2,2)
____ = b.add(path, {0, 1});   //  (1,1)-(2,2)-(0,1)
path = b.add({}, {2, 1});     // *(2,1)

IndexSet indexes;
for (auto path : b.paths) {
auto index = findIndexByString(b.model, path);
auto index2 = findIndexByString2(b.model, path);
Q_ASSERT(index.isValid());
Q_ASSERT(!indexes.contains(index));
Q_ASSERT(index2 == index);
indexes.insert(index);
}
}

Это завершает пример.

1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]