mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 09:33:25 -08:00
[CardDB] Sort power/toughness without splitting every comparison (#7607)
* [CardDB] Sort power/toughness without splitting every comparison
When the card database is sorted by the P/T column, lessThan() runs
O(n log n) times over the whole view, and each call split both strings
into a QStringList ("power/toughness") and then read the two elements
back. For the roughly 36k cards in the database that is millions of
list and string allocations, all for a value that contains at most one
separator.
The comparator now locates the single '/' with indexOf and passes the
two halves as QStringView to lessThanNumerically(), which is changed
from QString to QStringView. The numeric parsing, the "1+*" fallback and
the locale-aware tie-break are unchanged, and strings with no separator
or more than one still fall through to the plain string comparison,
exactly as split()-based size checks did before.
lessThanNumerically() also initializes its two bool out-parameters; they
were declared uninitialized, which only worked because toFloat() always
assigns them.
Compared old and new comparator over a corpus of 2024 power/toughness
strings (4096576 ordered pairs, including malformed values): zero
differences. Full build and ctest (628 tests) pass.
* Extract well-formedness checks in P/T comparator
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
e666a4978d
commit
4980f88a7a
2 changed files with 29 additions and 23 deletions
|
|
@ -82,13 +82,12 @@ int CardDatabaseDisplayModel::rowCount(const QModelIndex &parent) const
|
||||||
|
|
||||||
bool CardDatabaseDisplayModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
bool CardDatabaseDisplayModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||||
{
|
{
|
||||||
|
const QString leftString = sourceModel()->data(left, CardDatabaseModel::SortRole).toString();
|
||||||
QString leftString = sourceModel()->data(left, CardDatabaseModel::SortRole).toString();
|
const QString rightString = sourceModel()->data(right, CardDatabaseModel::SortRole).toString();
|
||||||
QString rightString = sourceModel()->data(right, CardDatabaseModel::SortRole).toString();
|
|
||||||
|
|
||||||
if (!cardName.isEmpty() && left.column() == CardDatabaseModel::NameColumn) {
|
if (!cardName.isEmpty() && left.column() == CardDatabaseModel::NameColumn) {
|
||||||
bool isLeftType = leftString.startsWith(cardName, Qt::CaseInsensitive);
|
const bool isLeftType = leftString.startsWith(cardName, Qt::CaseInsensitive);
|
||||||
bool isRightType = rightString.startsWith(cardName, Qt::CaseInsensitive);
|
const bool isRightType = rightString.startsWith(cardName, Qt::CaseInsensitive);
|
||||||
|
|
||||||
// test for an exact match: isLeftType && leftString.size() == cardName.size()
|
// test for an exact match: isLeftType && leftString.size() == cardName.size()
|
||||||
// or an exclusive start match: isLeftType && !isRightType
|
// or an exclusive start match: isLeftType && !isRightType
|
||||||
|
|
@ -101,31 +100,37 @@ bool CardDatabaseDisplayModel::lessThan(const QModelIndex &left, const QModelInd
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (right.column() == CardDatabaseModel::PTColumn && left.column() == CardDatabaseModel::PTColumn) {
|
} else if (right.column() == CardDatabaseModel::PTColumn && left.column() == CardDatabaseModel::PTColumn) {
|
||||||
QStringList leftList = leftString.split("/");
|
// The comparator runs O(n log n) times while the view is sorted, so
|
||||||
QStringList rightList = rightString.split("/");
|
// splitting every power/toughness string into a QStringList allocates
|
||||||
|
// a lot for data that has exactly one separator. Work on views and
|
||||||
|
// only split the two halves out when both strings are well formed.
|
||||||
|
const QStringView leftPT = leftString;
|
||||||
|
const QStringView rightPT = rightString;
|
||||||
|
const qsizetype leftSlash = leftPT.indexOf(u'/');
|
||||||
|
const qsizetype rightSlash = rightPT.indexOf(u'/');
|
||||||
|
const bool leftIsWellFormed = leftSlash >= 0 && leftPT.indexOf(u'/', leftSlash + 1) < 0;
|
||||||
|
const bool rightIsWellFormed = rightSlash >= 0 && rightPT.indexOf(u'/', rightSlash + 1) < 0;
|
||||||
|
|
||||||
if (leftList.size() == 2 && rightList.size() == 2) {
|
if (leftIsWellFormed && rightIsWellFormed) {
|
||||||
|
const int powerComparison = lessThanNumerically(leftPT.left(leftSlash), rightPT.left(rightSlash));
|
||||||
// cool, have both P/T in list now
|
if (powerComparison != 0) {
|
||||||
int lessThanNum = lessThanNumerically(leftList.at(0), rightList.at(0));
|
return powerComparison < 0;
|
||||||
if (lessThanNum != 0) {
|
|
||||||
return lessThanNum < 0;
|
|
||||||
} else {
|
|
||||||
// power equal, check toughness
|
|
||||||
return lessThanNumerically(leftList.at(1), rightList.at(1)) < 0;
|
|
||||||
}
|
}
|
||||||
|
// power equal, check toughness
|
||||||
|
return lessThanNumerically(leftPT.mid(leftSlash + 1), rightPT.mid(rightSlash + 1)) < 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return QString::localeAwareCompare(leftString, rightString) < 0;
|
return QString::localeAwareCompare(leftString, rightString) < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int CardDatabaseDisplayModel::lessThanNumerically(const QString &left, const QString &right)
|
int CardDatabaseDisplayModel::lessThanNumerically(QStringView left, QStringView right)
|
||||||
{
|
{
|
||||||
if (left == right) {
|
if (left == right) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool okLeft, okRight;
|
bool okLeft = false;
|
||||||
|
bool okRight = false;
|
||||||
float leftNum = left.toFloat(&okLeft);
|
float leftNum = left.toFloat(&okLeft);
|
||||||
float rightNum = right.toFloat(&okRight);
|
float rightNum = right.toFloat(&okRight);
|
||||||
|
|
||||||
|
|
@ -139,11 +144,11 @@ int CardDatabaseDisplayModel::lessThanNumerically(const QString &left, const QSt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// try and parsing again, for weird ones like "1+*"
|
// try and parsing again, for weird ones like "1+*"
|
||||||
QString leftAfterNum = "";
|
QStringView leftAfterNum;
|
||||||
QString rightAfterNum = "";
|
QStringView rightAfterNum;
|
||||||
if (!okLeft) {
|
if (!okLeft) {
|
||||||
int leftNumIndex = 0;
|
int leftNumIndex = 0;
|
||||||
for (; leftNumIndex < left.length(); leftNumIndex++) {
|
for (; leftNumIndex < left.size(); leftNumIndex++) {
|
||||||
if (!left.at(leftNumIndex).isDigit()) {
|
if (!left.at(leftNumIndex).isDigit()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -155,7 +160,7 @@ int CardDatabaseDisplayModel::lessThanNumerically(const QString &left, const QSt
|
||||||
}
|
}
|
||||||
if (!okRight) {
|
if (!okRight) {
|
||||||
int rightNumIndex = 0;
|
int rightNumIndex = 0;
|
||||||
for (; rightNumIndex < right.length(); rightNumIndex++) {
|
for (; rightNumIndex < right.size(); rightNumIndex++) {
|
||||||
if (!right.at(rightNumIndex).isDigit()) {
|
if (!right.at(rightNumIndex).isDigit()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QStringView>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <libcockatrice/card/card_localization.h>
|
#include <libcockatrice/card/card_localization.h>
|
||||||
#include <libcockatrice/filters/filter_string.h>
|
#include <libcockatrice/filters/filter_string.h>
|
||||||
|
|
@ -88,7 +89,7 @@ signals:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
[[nodiscard]] bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
[[nodiscard]] bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
||||||
static int lessThanNumerically(const QString &left, const QString &right);
|
static int lessThanNumerically(QStringView left, QStringView right);
|
||||||
[[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
[[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||||
[[nodiscard]] bool rowMatchesCardName(CardInfoPtr info) const;
|
[[nodiscard]] bool rowMatchesCardName(CardInfoPtr info) const;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue