【问题标题】:Sort filenames naturally with Qt使用 Qt 对文件名进行自然排序
【发布时间】:2012-08-09 15:46:51
【问题描述】:

我正在使用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 中是否有内置的方法来实现这一点,如果没有,在什么地方实现它?

【问题讨论】:

    标签: c++ qt natural-sort


    【解决方案1】:

    如果您想使用QCollatorQDir::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(),
        [&](const QString &file1, const QString &file2)
        {
            return collator.compare(file1, file2) < 0;
        });
    

    根据The Badger的评论,QCollator也可以直接作为std::sort的参数,替换lambda,所以对std::sort的调用变成:

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

    【讨论】:

    • 实际上不需要 lambda,因为 QCollat​​or 有一个未记录的operator()std::sort(entryList.begin(), entryList.end(), collator); 应该足够好。我认为在文档“QCollat​​or 对象可以与基于模板的排序算法(例如 std::sort )一起使用以对 QStrings 列表进行排序”中的以下行中暗示了这一点。
    【解决方案2】:

    这不是问题本身的答案,而是一些一般信息,以帮助其他偶然发现此问题的人,试图弄清楚如何“自然排序”。

    首先:这是不可能的。 “正确”的自然排序取决于上下文——缺少“真正的”人工智能——实际上是不可能拥有的。例如,如果我有一堆混合数字和字母的文件名,并且这些名称的某些部分恰好匹配[0-9a-f],那是十六进制数字吗? “1,500”是否与“1500”相同,或者“1”和“500”是单独的数字? “2019/06/07”是在“2019/07/06”之前还是之后? “1.21”与“1.5”怎么样? (提示:最后一个取决于这些是十进制数字还是语义版本号。)

    “解决”这个问题需要约束它;决定我们只处理特定情况,超出这些范围的任何事情都只会产生“错误”的答案。 (幸运的是,OP 的问题似乎已经满足了通常的一组约束。)

    也就是说,我相信QCollator 总体上运行良好(再次强调,它并不“真正”起作用,但它在普遍接受的限制范围内成功)。在“自己的解决方案”部门,还可以查看qtNaturalSort,这是我作为对不同(不是QCollator)算法的Qt-API 改进而编写的。 (在撰写本文时不支持不区分大小写,但欢迎使用补丁!)我付出了一大堆努力使其“正确”解析数字,甚至处理任意长度的数字和非 BMP 数字。

    【讨论】:

      【解决方案3】:

      Qt 在 Qt 5.2 之前没有自然排序实现,请参阅 this feature request

      从 Qt 5.2 开始,QCollator 在启用numeric mode 时允许自然排序。

      【讨论】:

      • 显然这正确答案,但由于Romário实际上提供了代码,他必须获得投票!
      【解决方案4】:

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

      【讨论】:

      • 这个实现似乎有问题。示例输入字符串:f3.ext, f10.ext, f100.ext, f1000.ext, f15.ext。我认为它首先推进到字符不再相等的地步是有问题的,因为这忽略了有价值的数字比较。我发现我需要始终尽可能多地使用数字。
      【解决方案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();
      }
      

      【讨论】:

      • 不确定此函数是否适用于所有情况,因为它只是从字符串中提取数字而不检查数字的实际位置。例如,它会将“aaa2,bbb1”排序为“bbb1,aaa2”,这是不正确的。
      【解决方案6】:

      是的,这是可能的。

      为此,您需要在构造QDir 时指定标志LocaleAware。目的。构造函数是

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

      你也可以使用

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

      【讨论】:

      • 谢谢,这改变了结果,但没有解决问题。现在文件名的排序如下:0_0815, 1_4711, 10_8234, 100_5978, ... .
      • 您可能需要自己对文件名进行排序。
      • 试试dir.setSorting(QDir::Name);dir.setSorting(QDir::LocaleAware|QDir::Name);。对不起,我现在不能检查,我必须离开
      • @elmigranto 我虽然是这样,但总的来说 Qt 提供了一种获得与本机应用程序完全相同的结果的方法。
      • 对不起,这对我不起作用。 QCollator 允许您设置数字模式; QDir::setSorting() 没有。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-10
      • 2013-09-06
      相关资源
      最近更新 更多