使用C++ Ranges从扁平化表构建层级树形结构
问题需求
基于MyStruct的sourcePath和functionName字段构建排序后的层级树形结构,忽略id字段。要求先按路径分组(去重唯一路径),再将对应函数名作为叶子节点添加,使用C++20 Ranges或Ranges-v3实现,已有正确排序比较器,需找到合适的视图组合方式。
结构体定义
using MyStruct = struct MyStruct { unsigned id; std::filesystem::path sourcePath; std::string functionName; auto operator<=>(const MyStruct&) const = default; };
初始化数据
std::vector<MyStruct> structs{ {1, "c:/temp/file3.c", "chimp()"}, {2, "c:/temp/file3.c", "ape()"}, {3, "c:/temp/file1.c", "foo()"}, {4, "c:/temp/file1.c", "bar()"}, {5, "c:/temp/file1.c", "baz()"}, {6, "c:/temp/sub/file3.c", "file3Fn2()"}, {7, "c:/temp/sub/file3.c", "file3Fn1()"}, {8, "c:/temp/file2.c", "file2Fn2()"}, {9, "c:/temp/file2.c", "file2Fn3()"}, {10, "c:/temp/file2.c", "file2Fn1()"}, };
期望树形结构
├───root ├───file1.c │ ├───foo() │ ├───bar() │ └───baz() ├───file2.c │ ├───file2Fn1() │ ├───file2Fn2() │ └───file2Fn3() ├───file3.c │ ├───ape() │ └───chimp() └───sub └───file3.c ├───file3Fn1() └───file3Fn2()
解决方案
步骤1:自定义排序比较器
默认太空船运算符会优先比较id,因此需要自定义比较器,先按sourcePath升序排序,再按functionName升序排序:
auto customComp = [](const MyStruct& a, const MyStruct& b) { if (a.sourcePath != b.sourcePath) { return a.sourcePath < b.sourcePath; } return a.functionName < b.functionName; };
步骤2:使用Ranges排序+分组
利用C++20 Ranges的views::sort完成排序,再用views::group_by按sourcePath分组(分组前必须确保数据已排序,否则分组结果不符合预期):
#include <ranges> #include <filesystem> #include <vector> #include <string> #include <iostream> #include <map> #include <set> // ... 结构体定义和初始化数据 ... int main() { const std::filesystem::path root = "c:/temp"; // 排序:按路径、再按函数名 auto sorted_structs = structs | std::views::sort(customComp); // 按sourcePath分组,每组对应一个路径下的所有函数 auto path_groups = sorted_structs | std::views::group_by( [](const MyStruct& lhs, const MyStruct& rhs) { return lhs.sourcePath == rhs.sourcePath; } );
步骤3:构建路径-函数映射
将分组后的视图转换为std::map,键为相对root的路径,值为该路径下的函数名列表,方便后续构建树形结构:
std::map<std::filesystem::path, std::vector<std::string>> path_func_map; for (const auto& group : path_groups) { const auto& base_struct = *group.begin(); auto rel_path = std::filesystem::relative(base_struct.sourcePath, root); std::vector<std::string> functions; for (const auto& s : group) { functions.push_back(s.functionName); } path_func_map[rel_path] = std::move(functions); }
步骤4:递归打印树形结构
编写递归函数遍历路径映射,按层级打印目录、文件和函数:
// 递归打印目录树的辅助函数 auto print_tree = [&](auto&& self, const std::filesystem::path& current_dir, int depth) -> void { // 打印当前节点 std::string indent(depth * 4, ' '); if (depth > 0) { indent.replace(indent.size() - 4, 4, "├───"); std::cout << indent << current_dir.filename().string() << "\n"; } else { std::cout << indent << "root\n"; } // 打印当前目录下的文件及对应函数 for (const auto& [path, funcs] : path_func_map) { if (path.parent_path() == current_dir && path.has_extension()) { std::string file_indent((depth + 1) * 4, ' '); std::cout << file_indent << "├───" << path.filename().string() << "\n"; for (const auto& fn : funcs) { std::cout << std::string((depth + 2) * 4, ' ') << "├───" << fn << "\n"; } } } // 收集并打印当前目录下的子目录 std::set<std::filesystem::path> subdirs; for (const auto& [path, _] : path_func_map) { auto parent = path.parent_path(); if (parent == current_dir && !path.has_extension()) { subdirs.insert(path); } else if (parent.parent_path() == current_dir) { subdirs.insert(parent); } } for (const auto& subdir : subdirs) { self(self, subdir, depth + 1); } }; // 从root开始打印 print_tree(print_tree, "", 0); return 0; }
代码说明
- 排序与分组:先排序保证
group_by能正确按路径聚合所有同路径的结构体; - 路径映射:将绝对路径转换为相对root的路径,简化层级处理;
- 递归打印:先处理当前目录下的文件和函数,再处理子目录,确保树形结构的层级正确。
内容的提问来源于stack exchange,提问作者johnco3
相关产品推荐
相关产品推荐

