Improve natural sort algorithm

1. Use proper case folding function instead of `toLower()`.
2. Use locale aware comparison instead of comparing unicode code points.
   Now `a` comes before `A` which is the same as the result from QCollator. A nice side effect
   is now it properly compares locale specific characters (for example `C`, `Č`).
3. Improve testing. Now the test is runnable and stable on all platforms.

PR  #20208.
This commit is contained in:
Chocobo1 2024-01-08 14:47:00 +08:00 committed by GitHub
parent e69f857828
commit 5b3b56c918
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 42 additions and 34 deletions

View file

@ -26,15 +26,16 @@
* exception statement from your version.
*/
#include <tuple>
#include <QLocale>
#include <QObject>
#include <QTest>
#include "base/global.h"
// only test qbt own implementation, not QCollator
#define QBT_USE_QCOLLATOR 0
#include "base/utils/compare.h"
#ifndef QBT_USE_QCOLLATOR // only test qbt own implementation, not QCollator
namespace
{
enum class CompareResult
@ -59,8 +60,8 @@ namespace
{u"a"_s, u""_s, CompareResult::Greater, CompareResult::Greater},
{u"a"_s, u"a"_s, CompareResult::Equal, CompareResult::Equal},
{u"A"_s, u"a"_s, CompareResult::Equal, CompareResult::Less}, // ascii code of 'A' is smaller than 'a'
{u"a"_s, u"A"_s, CompareResult::Equal, CompareResult::Greater},
{u"A"_s, u"a"_s, CompareResult::Equal, CompareResult::Greater},
{u"a"_s, u"A"_s, CompareResult::Equal, CompareResult::Less},
{u"0"_s, u"0"_s, CompareResult::Equal, CompareResult::Equal},
{u"1"_s, u"0"_s, CompareResult::Greater, CompareResult::Greater},
@ -71,17 +72,17 @@ namespace
{u"😁"_s, u"😀"_s, CompareResult::Greater, CompareResult::Greater},
{u"a1"_s, u"a1"_s, CompareResult::Equal, CompareResult::Equal},
{u"A1"_s, u"a1"_s, CompareResult::Equal, CompareResult::Less},
{u"a1"_s, u"A1"_s, CompareResult::Equal, CompareResult::Greater},
{u"A1"_s, u"a1"_s, CompareResult::Equal, CompareResult::Greater},
{u"a1"_s, u"A1"_s, CompareResult::Equal, CompareResult::Less},
{u"a1"_s, u"a2"_s, CompareResult::Less, CompareResult::Less},
{u"A1"_s, u"a2"_s, CompareResult::Less, CompareResult::Less},
{u"a1"_s, u"A2"_s, CompareResult::Less, CompareResult::Greater},
{u"A1"_s, u"a2"_s, CompareResult::Less, CompareResult::Greater},
{u"a1"_s, u"A2"_s, CompareResult::Less, CompareResult::Less},
{u"A1"_s, u"A2"_s, CompareResult::Less, CompareResult::Less},
{u"abc100"_s, u"abc99"_s, CompareResult::Greater, CompareResult::Greater},
{u"ABC100"_s, u"abc99"_s, CompareResult::Greater, CompareResult::Less},
{u"abc100"_s, u"ABC99"_s, CompareResult::Greater, CompareResult::Greater},
{u"ABC100"_s, u"abc99"_s, CompareResult::Greater, CompareResult::Greater},
{u"abc100"_s, u"ABC99"_s, CompareResult::Greater, CompareResult::Less},
{u"ABC100"_s, u"ABC99"_s, CompareResult::Greater, CompareResult::Greater},
{u"100abc"_s, u"99abc"_s, CompareResult::Greater, CompareResult::Greater},
@ -135,7 +136,6 @@ namespace
}
}
}
#endif
class TestUtilsCompare final : public QObject
{
@ -145,8 +145,20 @@ class TestUtilsCompare final : public QObject
public:
TestUtilsCompare() = default;
#ifndef QBT_USE_QCOLLATOR // only test qbt own implementation, not QCollator
private slots:
void initTestCase() const
{
// Test will fail if ran with `C` locale. This is because `C` locale compare chars by code points
// and doesn't take account of human expectations
QLocale::setDefault(QLocale::English);
}
void cleanupTestCase() const
{
// restore global state
QLocale::setDefault(QLocale::system());
}
void testNaturalCompareCaseInsensitive() const
{
const Utils::Compare::NaturalCompare<Qt::CaseInsensitive> cmp;
@ -178,7 +190,6 @@ private slots:
for (const TestData &data : testData)
testLessThan(data, cmp(data.lhs, data.rhs), data.caseSensitiveResult);
}
#endif
};
QTEST_APPLESS_MAIN(TestUtilsCompare)