Естественный вид имен файлов каталогов в C ++ - PullRequest
7 голосов
/ 16 марта 2012

У меня есть список каталогов, для которого я хочу получить имена файлов и поместить их в вектор строк, чтобы они сортировались «естественным» образом.например, { "10.txt" "0.txt" "2.txt" "1.m" "Jan12" "July13.txt" "Nov25.txt" "Jane" "John" } должно быть {"0.txt" "1.m" "2.txt" "10.txt" "Jan12" "July13.txt" "Nov25.txt" "Jane" "John" }.Какой самый простой способ сделать это?

При разработке «натурального» мы предполагаем строку, состоящую из частей чисел (N) и текста (T) такой, что ...(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

Ответы [ 4 ]

11 голосов
/ 17 марта 2012

Вам нужна функция, которая делает естественное сравнение между двумя строками. После этого вы можете использовать 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
7 голосов
/ 17 марта 2012

Есть функция, которая делает именно то, что вы хотите в glibc. К сожалению, это C, а не C ++, поэтому, если вы можете жить с этим, вот самое простое из возможных решений «из коробки», без каких-либо изменений и повторного изобретения колеса. Кстати: это именно так, как ls -lv реализовано. Самая важная часть этого - функция 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("%s\n", namelist[i]->d_name);
            free(namelist[i]);
        }
        free(namelist);
    }
    return 0;
}
3 голосов
/ 17 марта 2012

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

выход: * +1010 *

1.txt
1_t.txt
3.txt
4.txt
10.txt
10_t.txt
13.txt
20.txt
1 голос
/ 23 ноября 2015

Я столкнулся с алгоритмом, который работает довольно хорошо: http://sourcefrog.net/projects/natsort/

Я немного изменил источник для удовлетворения моих потребностей:

  1. передать std::string в качестве параметра.
  2. используется с std::sort и std::vector.

Моя версия источника здесь .

Пример использования:

#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

Это заголовок strnatcmp.hpp .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...