Сортировка имен файлов с помощью Qt
Я читаю содержимое каталогов, используя QDir::entryList()
. Имена файлов внутри структурированы следующим образом:
index_randomNumber.png
Мне нужно, чтобы они отсортировались по index
, как Windows Explorer сортировал файлы, чтобы получить
0_0815.png
1_4711.png
2_2063.png
...
вместо того, что дает сортировка QDir::Name
:
0_0815.png
10000_6661.png
10001_7401.png
...
Есть ли встроенный способ в Qt для достижения этого, а если нет, то в каком правильном месте его реализовать?
Ответы
Ответ 1
Если вы хотите использовать QCollator
для сортировки записей из списка записей, возвращаемых QDir::entryList
, вы можете отсортировать результат с помощью std::sort()
:
dir.setFilter(QDir::Files | QDir::NoSymLinks);
dir.setSorting(QDir::NoSort); // will sort manually with std::sort
auto entryList = dir.entryList();
QCollator collator;
collator.setNumericMode(true);
std::sort(
entryList.begin(),
entryList.end(),
[&collator](const QString &file1, const QString &file2)
{
return collator.compare(file1, file2) < 0;
});
Согласно комментарию The Badger, QCollator
может также использоваться непосредственно в качестве аргумента std::sort
, заменяя лямбду, поэтому последняя строка становится:
std::sort(entryList.begin(), entryList.end(), collator);
Ответ 2
Qt не выполнял естественную сортировку до Qt 5.2, см. этот запрос функции.
Так как Qt 5.2 существует QCollator, который позволяет естественную сортировку, когда числовой режим.
Ответ 3
Да, это возможно.
Для этого вам нужно указать флаг LocaleAware при построении QDir
. объект. Конструктор
QDir(const QString & path, const QString & nameFilter, SortFlags sort = SortFlags( Name | IgnoreCase ), Filters filters = AllEntries)
Вы также можете использовать
QDir dir;
dir.setSorting(QDir::LocaleAware);
Ответ 4
Это не ответ на вопрос как таковой, но некоторая общая информация в интересах других, которые натыкаются на это, пытаясь понять, как "сортировать естественным образом".
Во-первых, это невозможно. "Правильная" естественная сортировка зависит от контекста, который - за исключением "истинного" искусственного интеллекта - практически невозможен. Например, если у меня есть набор имен файлов со смешанными цифрами и буквами, и некоторые части этих имен совпадают с [0-9a-f]
, это шестнадцатеричное число? "1500" - это то же самое, что "1500", или "1" и "500" - отдельные номера? "2019/06/07" наступает до или после "2019/07/06"? А как насчет "1,21" против "1,5"? (Подсказка: последнее зависит от того, являются ли они десятичными или семантическими номерами версий.)
"Решение" этой проблемы требует ее ограничения; решив, что мы будем обрабатывать только конкретные случаи, и все, что находится за этими пределами, просто даст "неправильный" ответ. (К счастью, проблема ОП уже удовлетворяет обычному набору ограничений.)
Тем не менее, я считаю, что QCollator
целом работает хорошо (опять же, в том смысле, что он не "действительно" работает, но он выполняется в рамках общепринятых ограничений). В отделе "собственных решений" взгляните также на qtNaturalSort, который я написал как улучшение Qt-API по сравнению с другим (не QCollator
) алгоритмом. (Нечувствительность к регистру не поддерживается на момент написания, но исправления приветствуются!) Я приложил немало усилий, чтобы заставить его анализировать "правильно", даже обрабатывая числа произвольной длины и цифры, отличные от BMP.
Ответ 5
inline int findNumberPart(const QString& sIn)
{
QString s = "";
int i = 0;
bool isNum = false;
while (i < sIn.length())
{
if (isNum)
{
if (!sIn[i].isNumber())
break;
s += sIn[i];
}
else
{
if (sIn[i].isNumber())
s += sIn[i];
}
++i;
}
if (s == "")
return 0;
return s.toInt();
}
bool naturalSortCallback(const QString& s1, const QString& s2)
{
int idx1 = findNumberPart(s1);
int idx2 = findNumberPart(s2);
return (idx1 < idx2);
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QDir dir(MYPATH);
QStringList list = dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot);
qSort(list.begin(), list.end(), naturalSortCallback);
foreach(QString s, list)
qDebug() << s << endl;
return a.exec();
}
Ответ 6
Qt не поддерживает естественную сортировку изначально, но ее можно легко реализовать. Например, это можно использовать для сортировки QStringList
:
struct naturalSortCompare {
inline bool isNumber(QChar c) {
return c >= '0' && c <= '9';
}
inline bool operator() (const QString& s1, const QString& s2) {
if (s1 == "" || s2 == "") return s1 < s2;
// Move to the first difference between the strings
int startIndex = -1;
int length = s1.length() > s2.length() ? s2.length() : s1.length();
for (int i = 0; i < length; i++) {
QChar c1 = s1[i];
QChar c2 = s2[i];
if (c1 != c2) {
startIndex = i;
break;
}
}
// If the strings are the same, exit now.
if (startIndex < 0) return s1 < s2;
// Now extract the numbers, if any, from the two strings.
QString sn1;
QString sn2;
bool done1 = false;
bool done2 = false;
length = s1.length() < s2.length() ? s2.length() : s1.length();
for (int i = startIndex; i < length; i++) {
if (!done1 && i < s1.length()) {
if (isNumber(s1[i])) {
sn1 += QString(s1[i]);
} else {
done1 = true;
}
}
if (!done2 && i < s2.length()) {
if (isNumber(s2[i])) {
sn2 += QString(s2[i]);
} else {
done2 = true;
}
}
if (done1 && done2) break;
}
// If none of the strings contain a number, use a regular comparison.
if (sn1 == "" && sn2 == "") return s1 < s2;
// If one of the strings doesn't contain a number at that position,
// we put the string without number first so that, for example,
// "example.bin" is before "example1.bin"
if (sn1 == "" && sn2 != "") return true;
if (sn1 != "" && sn2 == "") return false;
return sn1.toInt() < sn2.toInt();
}
};
Тогда использование просто:
std::sort(stringList.begin(), stringList.end(), naturalSortCompare());