C++中目录文件名的自然排序
Natural Sort of Directory Filenames in C++
我有一个目录列表,我想为其检索文件名,并将它们放在字符串向量中,以便以"自然"的方式进行排序。例如CCD_ 1应该是CCD_。最简单的方法是什么?
在详细阐述"自然"时,我们假设由数字(N)和文本的部分组成的字符串的...(N)(T)...
,那么对于...(N1)(T1)...
和...(N2)(T2)...
将是(N1<N2) (<) (T1<T2)
,其中(<)
意味着左项优先于右项。在这种情况下,如果数字在字符串中的位置相同,即1.z (<) 1_t.txt
,则数字优先于文本字段。
是否已经有一个库函数可以对字母数字字符串或目录条目进行这种排序?
所需的文件顺序。文件名将存储在字符串向量中。
Abhinav@Abhinav-PC /cygdrive/c/AbhinavSamples/shell
$ ls -lv
total 8
-rw-r--r--+ 1 Abhinav None 2 Mar 17 00:51 1.txt
-rw-r--r--+ 1 Abhinav None 2 Mar 17 00:55 1_t.txt
-rw-r--r--+ 1 Abhinav None 2 Mar 17 00:50 3.txt
-rw-r--r--+ 1 Abhinav None 2 Mar 17 00:51 4.txt
-rw-r--r--+ 1 Abhinav None 2 Mar 17 00:53 10.txt
-rw-r--r--+ 1 Abhinav None 2 Mar 17 00:56 10_t.txt
-rw-r--r--+ 1 Abhinav None 2 Mar 17 00:56 13.txt
-rw-r--r--+ 1 Abhinav None 2 Mar 17 00:53 20.txt
**Simple Sort**
Abhi@Abhi-PC /cygdrive/c/AbhinavSamples/shell
$ ls -l
total 8
-rw-r--r--+ 1 Abhinav None 2 Mar 17 00:51 1.txt
-rw-r--r--+ 1 Abhinav None 2 Mar 17 00:53 10.txt
-rw-r--r--+ 1 Abhinav None 2 Mar 17 00:56 10_t.txt
-rw-r--r--+ 1 Abhinav None 2 Mar 17 00:56 13.txt
-rw-r--r--+ 1 Abhinav None 2 Mar 17 00:55 1_t.txt
-rw-r--r--+ 1 Abhinav None 2 Mar 17 00:53 20.txt
-rw-r--r--+ 1 Abhinav None 2 Mar 17 00:50 3.txt
-rw-r--r--+ 1 Abhinav None 2 Mar 17 00:51 4.txt
您需要一个函数来对两个字符串进行自然比较。之后,您可以使用带有比较函数的std::sort
作为第三个参数(正如@chac已经指出的那样)。在下文中,我尝试以递归的方式实现这样一个函数。注意,它可以处理不需要以数字部分开头和以字符串部分结尾的任意文件名:
bool compareNat(const std::string& a, const std::string& b)
{
if (a.empty())
return true;
if (b.empty())
return false;
if (std::isdigit(a[0]) && !std::isdigit(b[0]))
return true;
if (!std::isdigit(a[0]) && std::isdigit(b[0]))
return false;
if (!std::isdigit(a[0]) && !std::isdigit(b[0]))
{
if (std::toupper(a[0]) == std::toupper(b[0]))
return compareNat(a.substr(1), b.substr(1));
return (std::toupper(a[0]) < std::toupper(b[0]));
}
// Both strings begin with digit --> parse both numbers
std::istringstream issa(a);
std::istringstream issb(b);
int ia, ib;
issa >> ia;
issb >> ib;
if (ia != ib)
return ia < ib;
// Numbers are the same --> remove numbers and recurse
std::string anew, bnew;
std::getline(issa, anew);
std::getline(issb, bnew);
return (compareNat(anew, bnew));
}
这里有一个简单的测试用例:
#include <iostream> // std::cout
#include <string>
#include <algorithm> // std::sort, std::copy
#include <iterator> // std::ostream_iterator
#include <sstream> // std::istringstream
#include <vector>
#include <cctype> // std::isdigit
int main()
{
std::vector<std::string> str;
str.push_back("20.txt");
str.push_back("10.txt");
str.push_back("1.txt");
str.push_back("z2.txt");
str.push_back("z10.txt");
str.push_back("z100.txt");
str.push_back("1_t.txt");
std::sort(str.begin(), str.end(), compareNat);
std::copy(str.begin(), str.end(),
std::ostream_iterator<std::string>(std::cout, "n"));
}
结果是:
1.txt
1_t.txt
10.txt
20.txt
z2.txt
z10.txt
z100.txt
在glibc中有一个函数可以执行您想要的操作。不幸的是,它是C,而不是C++,所以如果你能接受它,这里有一个最简单的"开箱即用"的解决方案,而不需要重新实现任何东西和重新发明轮子。BTW:这正是{ "10.txt" "0.txt" "2.txt" "1.m" "Jan12" "July13.txt" "Nov25.txt" "Jane" "John" }
0的实现方式。它最重要的部分是versionsort
函数,它为您执行自然排序。它在这里被用作scandir
的比较函数。下面的简单示例打印当前目录中的所有文件/目录,按您的意愿排序。
#define _GNU_SOURCE
#include <dirent.h>
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
struct dirent **namelist;
int n,i;
n = scandir(".", &namelist, 0, versionsort);
if (n < 0)
perror("scandir");
else
{
for(i =0 ; i < n; ++i)
{
printf("%sn", namelist[i]->d_name);
free(namelist[i]);
}
free(namelist);
}
return 0;
}
您可以使用std::sort,将文件名拆分为数字+字符串(两者都是可选的)。
#include <vector>
#include <string>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <cstring>
using namespace std;
bool compare_filenames(string a, string b)
{
char *pA, *pB;
long A = strtol(a.c_str(), &pA, 10),
B = strtol(b.c_str(), &pB, 10);
if (A < B)
return true;
if (A == B)
return strcmp(pA, pB);
return false;
}
int main_compare_filenames(int, char **)
{
const char *v[] ={
"1.txt",
"10.txt",
"10_t.txt",
"13.txt",
"1_t.txt",
"20.txt",
"3.txt",
"4.txt"
};
vector<string> t(v, v + 8);
sort(t.begin(), t.end(), compare_filenames);
copy(t.begin(), t.end(), ostream_iterator<string>(cout, "n"));
return 0;
}
输出:
1_t.txt
1.txt
3.txt
4.txt
10_t.txt
10.txt
13.txt
20.txt
这几乎可行,但存在"_"先于"."的问题,因此需要进一步调整:
string remove_dot(const char *p)
{
const char *dot = strchr(p, '.');
return dot ? string(p, dot - p) : string(p);
}
bool compare_filenames(string a, string b)
{
char *pA, *pB;
long A = strtol(a.c_str(), &pA, 10),
B = strtol(b.c_str(), &pB, 10);
if (A < B)
return true;
if (A == B)
return remove_dot(pA) < remove_dot(pB);
return false;
}
输出:
1.txt
1_t.txt
3.txt
4.txt
10.txt
10_t.txt
13.txt
20.txt
我发现了一种运行良好的算法:http://sourcefrog.net/projects/natsort/
为了满足我的需求,我对来源进行了一些修改:
- 传递CCD_ 13作为参数
- 与CCD_ 14和CCD_
我的来源版本在这里。
一个用例示例:
#include <iostream>
#include <vector>
#include <algorithm>
#include "strnatcmp.hpp"
int main(){
std::vector<std::string> files;
files.push_back("20.txt");
files.push_back("10.txt");
files.push_back("1.txt");
files.push_back("z2.txt");
files.push_back("z10.txt");
files.push_back("z100.txt");
files.push_back("1_t.txt ");
files.push_back("ABc");
files.push_back("aBCd");
files.push_back("aBc");
files.push_back("aaa");
files.push_back("aBcd");
files.push_back("aaA");
std::sort(files.begin(),files.end(),compareNat);
for(int i=0;i<(int)files.size();i++)std::cout<< files[i]+"n";
return 0;
}
输出:
1.txt
1_t.txt
10.txt
20.txt
aaa
aaA
ABc
aBc
aBCd
aBcd
z2.txt
z10.txt
z100.txt
这是strnatcp.hpp标头。
- 二叉排序树无法编译
- 仅使用绝对值对数组进行排序,并在C++中显示实际值
- C++选择排序算法中的逻辑错误
- 使用C++程序合并排序没有得到正确的输出
- 计算排序向量的向量中唯一值的计数
- 排序算法c++
- 使用2个键的cpp-stl::优先级队列排序不正确
- 将结构向量排序为子组
- 在c++中尝试对对象数组进行排序时,出现std:bad_alloc错误
- 如何对点云数据进行排序
- 对字符串进行排序时,在c++中处理sort()
- 是否有类似std::lower_bound的函数,而不需要排序/分区输入
- 下面是排序算法O(n)吗
- 在自定义程序中使用本机 Windows 自然顺序排序
- 使用 Qt 对文件名进行自然排序
- C++中目录文件名的自然排序
- 自然排序的带有数字的文件名发出c++
- 没有自然排序顺序的两个std::向量之间的差
- 归并排序函数(自然归并排序)
- 在c++中使用StrCmpLogicalW函数进行自然排序