Сортировать имена файлов естественно с Qt

Я читаю содержимое каталогов с помощью QDir::entryList(). Имена файлов внутри структурированы следующим образом:

index_randomNumber.png

мне нужно их отсортировать по index, как проводник Windows будет сортировать файлы, чтобы я получил

0_0815.png
1_4711.png
2_2063.png
...

вместо того, что сортировка по QDir::Name дает мне:

0_0815.png
10000_6661.png
10001_7401.png
...

есть ли встроенный способ в Qt для достижения этого, и если нет, то в каком месте его реализовать?

5 ответов


если вы хотите использовать 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;
    });

по данным Барсукс комментарием QCollator также может использоваться непосредственно в качестве аргумента для std::sort, заменяя лямбду, поэтому последняя строка становится:

std::sort(entryList.begin(), entryList.end(), collator);

Qt не имел естественной реализации сортировки до Qt 5.2, см. этот запрос функции.

с Qt 5.2 есть QCollator что позволяет естественную сортировку, когда цифровой режим это.


Да, это возможно.

для этого необходимо указать флаг LocaleAware при строительстве QDir. объект. Конструктор

 QDir(const QString & path, const QString & nameFilter, SortFlags sort = SortFlags( Name | IgnoreCase ), Filters filters = AllEntries)

вы также можете использовать

QDir dir;
dir.setSorting(QDir::LocaleAware);

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();
}

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());