Sort subfolders and filenames of a directory numerically using boost::filesystem

1.8k Views Asked by At

I am using the same example as in the boost tutorials. But since my file names are numbered (1,20,23,..). The code fails to compare the strings(ex. 20 < 7). Is there a way to compare the directory_iteration numerically. Here is a snippet of the code

else if (is_directory(p))      // is p a directory?
      {
        cout << p << " is a directory containing:\n";

        typedef vector<path> vec;             // store paths,
        vec v;                                // so we can sort them later

        copy(directory_iterator(p), directory_iterator(), back_inserter(v));

        sort(v.begin(), v.end());             //  **I want to sort this numerically**

        for (vec::const_iterator it(v.begin()), it_end(v.end()); it != it_end; ++it)
        {
          cout << "   " << *it << '\n';
        }
      }

The layout of Directory and subfolder is shown here:

root/
    1/
     1.bmp
     2.bmp
     3.bmp
     4.bmp
     ...
    2/
     1.bmp
     2.bmp
     3.bmp
     4.bmp
    ....
    3/
     1.bmp
     2.bmp
     3.bmp
     4.bmp
     ....
1

There are 1 best solutions below

2
On BEST ANSWER

Since you cannot change the format of the filenames to be sortable as is, you will need to do some processing yourself -- parse the number from each file name, and use that for sorting.

Two approaches come to mind, trading off between memory and CPU usage.

Approach 1:

Store pairs of filename and numerical value, parsing when populating the vector.

Approach 2:

Store just the paths and perform conversion during comparison.


Code:

#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <iostream>

namespace fs = boost::filesystem;

int parse_filename(fs::path const& p)
{
    return std::stoi(p.filename().string());
}

void sort_numeric_1(fs::path const& p)
{
    typedef std::pair<fs::path, int> file_entry;
    typedef std::vector<file_entry> vec;
    vec v;

    for (fs::directory_iterator it(p); it != fs::directory_iterator(); ++it) {
        v.emplace_back(*it, parse_filename(*it));
    }

    std::sort(v.begin(), v.end()
        , [](file_entry const& a, file_entry const& b) {
        return a.second < b.second;
    });

    for (vec::const_iterator it(v.begin()), it_end(v.end()); it != it_end; ++it) {
        std::cout << "   " << it->first << '\n';
    }
}

void sort_numeric_2(fs::path const& p)
{
    typedef std::vector<fs::path> vec;
    vec v;

    std::copy(fs::directory_iterator(p), fs::directory_iterator(), back_inserter(v));

    std::sort(v.begin(), v.end()
        , [](fs::path const& a, fs::path const& b) {
        return std::stoi(a.filename().string()) < std::stoi(b.filename().string());
    });

    for (vec::const_iterator it(v.begin()), it_end(v.end()); it != it_end; ++it) {
        std::cout << "   " << *it << '\n';
    }
}

int main()
{
    sort_numeric_1("test");
    std::cout <<"\n";
    sort_numeric_2("test");
}

Contents of the directory:

> ls test
1.txt  10.txt  127.txt  20.txt  23.txt

Output:

"test\1.txt"
"test\10.txt"
"test\20.txt"
"test\23.txt"
"test\127.txt"

"test\1.txt"
"test\10.txt"
"test\20.txt"
"test\23.txt"
"test\127.txt"

Updating it to handle the whole directory structure you've shown, you could have something like this:

  • first find all the directories, and sort them numerically
  • then sort files in each of the found directories
  • merge all the sorted lists of files

Example:

#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <iostream>

namespace fs = boost::filesystem;

typedef std::vector<fs::path> path_vec;

void sort_numeric(path_vec& v)
{
    std::sort(v.begin(), v.end()
        , [](fs::path const& a, fs::path const& b) {
        return std::stoi(a.filename().string()) < std::stoi(b.filename().string());
    });
}

path_vec sort_root_dir(fs::path const& p)
{
    path_vec dirs;

    for (fs::directory_iterator it(p); it != fs::directory_iterator(); ++it) {
        if (is_directory(*it)) {
            dirs.emplace_back(*it);
        }
    }

    sort_numeric(dirs);

    path_vec files;
    for (path_vec::const_iterator it(dirs.begin()), it_end(dirs.end()); it != it_end; ++it) {
        path_vec dir_files;
        std::copy(fs::directory_iterator(*it), fs::directory_iterator(), back_inserter(dir_files));
        sort_numeric(dir_files);
        files.insert(files.end(), dir_files.begin(), dir_files.end());
    }

    return files;
}

int main()
{
    path_vec files = sort_root_dir("test");

    for (auto const& f : files) {
        std::cout << f << "\n";
    }
}