c++ - Ordenar los nombres de los archivos de forma natural con Qt
natural-sort (5)
Estoy leyendo un contenido de directorios usando QDir::entryList()
. Los nombres de archivo dentro están estructurados así:
index_randomNumber.png
Los necesito ordenados por index
, la forma en que el Explorador de Windows ordenaría los archivos para que yo obtenga
0_0815.png
1_4711.png
2_2063.png
...
En lugar de lo que la clasificación por QDir::Name
me da:
0_0815.png
10000_6661.png
10001_7401.png
...
¿Existe una forma integrada en Qt para lograr esto y, de no ser así, cuál es el lugar adecuado para implementarlo?
Qt no admite la clasificación natural de forma nativa, pero puede implementarse con bastante facilidad. Por ejemplo, esto se puede usar para ordenar una 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();
}
};
Entonces el uso es simplemente:
std::sort(stringList.begin(), stringList.end(), naturalSortCompare());
Qt no tuvo una implementación de orden natural hasta Qt 5.2, consulte esta solicitud de función .
Desde Qt 5.2 hay QCollator que permite una clasificación natural cuando se habilita el modo numérico .
Sí, es posible.
Para hacer eso, necesita especificar el indicador LocaleAware al construir el QDir
. objeto. El constructor es
QDir(const QString & path, const QString & nameFilter, SortFlags sort = SortFlags( Name | IgnoreCase ), Filters filters = AllEntries)
También puedes usar
QDir dir;
dir.setSorting(QDir::LocaleAware);
Si desea utilizar QCollator
para ordenar las entradas de la lista de entradas devueltas por QDir::entryList
, puede ordenar el resultado con 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;
});
De acuerdo con el comentario de The Badger , QCollator
también se puede usar directamente como un argumento para std::sort
, reemplazando la lambda, por lo que la última línea se convierte en:
std::sort(entryList.begin(), entryList.end(), collator);
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();
}