如何用Boost Graphviz无需预定义读取.dot文件的所有属性
问题描述
我想加载.dot文件时,不用预先定义属性就能读取所有属性。比如不想在代码里指定读取weight属性,因为不同数据集属性不一样,不想反复修改结构体加变量。
我参考过相关解决方案,但适配不了我的场景:我的属性都在边上,而且是数值类型(比如[weight=1])。我尝试修改代码里return boost::make_shared<dynamic_property_map_impl<unsigned int, std::string>>();的模板类型,但始终只能拿到node_id。
完整代码
#include <boost/graph/graphviz.hpp> #include <boost/program_options.hpp> #include <boost/exception/exception.hpp> #include <boost/exception/diagnostic_information.hpp> #include "read_graphviz_new.cpp" template<typename TKey, typename TValue> class dynamic_property_map_impl : public boost::dynamic_property_map { std::map<TKey, TValue> m_map; public: boost::any get(const boost::any& key) override { return m_map[boost::any_cast<TKey>(key)]; } std::string get_string(const boost::any& key) override { std::ostringstream s; s << m_map[boost::any_cast<TKey>(key)]; return s.str(); } void put(const boost::any& key, const boost::any& value) override { m_map[boost::any_cast<TKey>(key)] = boost::any_cast<TValue>(value); } const std::type_info& key() const override { return typeid(TKey); } const std::type_info& value() const override { return typeid(TValue); } }; boost::shared_ptr<boost::dynamic_property_map> handle_custom_properties(const std::string&, const boost::any&, const boost::any&) { return boost::make_shared<dynamic_property_map_impl<unsigned int, std::string>>(); } struct Vertex { std::string name; }; struct Edge { float weight = 1.f; }; typedef boost::property<boost::graph_name_t, std::string> graph_p; typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, Vertex, Edge, graph_p> graph_u; void Graph::Load(std::string filepath) { std::ifstream file(filepath); graph_u graph; try { boost::dynamic_properties dp(handle_custom_properties); dp.property("node_id", get(&Vertex::name, graph)); //dp.property("weight", get(&Edge::weight, graph)); boost::read_graphviz(file, graph, dp); } catch (std::exception& e) { std::cerr << e.what() << std::endl; } catch (boost::exception& e) { std::cerr << boost::diagnostic_information(e) << std::endl; } }
数据集示例
strict graph { 1; 2; 3; ... 1 -- 2 [weight=1]; 2 -- 3 [weight=8]; ... }
解决方案
核心问题在于你的自定义属性映射只处理了字符串类型值,且没有区分节点和边的属性键类型。Boost Graph的边属性键用的是graph_u::edge_descriptor,而不是单纯的unsigned int,之前的键类型不匹配导致无法捕获边属性。
步骤1:修改动态属性映射,支持边键和数值转换
重新实现dynamic_property_map_impl,兼容边描述符键,并增加字符串到数值的自动解析:
#include <boost/any.hpp> #include <sstream> #include <map> template<typename KeyType, typename ValueType> class dynamic_property_map_impl : public boost::dynamic_property_map { std::map<KeyType, ValueType> m_storage; public: boost::any get(const boost::any& key) override { auto typed_key = boost::any_cast<KeyType>(key); auto it = m_storage.find(typed_key); return it != m_storage.end() ? boost::any(it->second) : boost::any(ValueType{}); } std::string get_string(const boost::any& key) override { std::ostringstream oss; oss << boost::any_cast<ValueType>(get(key)); return oss.str(); } void put(const boost::any& key, const boost::any& value) override { try { m_storage[boost::any_cast<KeyType>(key)] = boost::any_cast<ValueType>(value); } catch (const boost::bad_any_cast&) { // 处理字符串转数值的情况 std::string str_val = boost::any_cast<std::string>(value); std::istringstream iss(str_val); ValueType typed_val; iss >> typed_val; m_storage[boost::any_cast<KeyType>(key)] = typed_val; } } const std::type_info& key() const override { return typeid(KeyType); } const std::type_info& value() const override { return typeid(ValueType); } };
步骤2:区分节点/边属性的处理函数
修改handle_custom_properties,根据属性所属的元素类型返回对应映射:
#include <boost/graph/graph_traits.hpp> boost::shared_ptr<boost::dynamic_property_map> handle_custom_properties(const std::string&, const boost::any& elem_desc, const boost::any&) { if (elem_desc.type() == typeid(graph_u::vertex_descriptor)) { // 节点属性用顶点描述符作为键,值存字符串 return boost::make_shared<dynamic_property_map_impl<graph_u::vertex_descriptor, std::string>>(); } else if (elem_desc.type() == typeid(graph_u::edge_descriptor)) { // 边属性用边描述符作为键,值存double兼容多数数值类型 return boost::make_shared<dynamic_property_map_impl<graph_u::edge_descriptor, double>>(); } return boost::make_shared<dynamic_property_map_impl<unsigned int, std::string>>(); }
步骤3:修改Edge结构体存储动态属性
把固定的weight字段改成std::map,用来存储任意边属性:
struct Vertex { std::string name; std::map<std::string, std::string> node_props; // 可选:存储节点动态属性 }; struct Edge { std::map<std::string, double> edge_props; // 存储所有边属性 };
步骤4:读取后提取动态属性到结构体
在read_graphviz执行完成后,遍历所有边,把动态属性存入Edge的map中:
void Graph::Load(std::string filepath) { std::ifstream file(filepath); graph_u graph; try { boost::dynamic_properties dp(handle_custom_properties); dp.property("node_id", get(&Vertex::name, graph)); boost::read_graphviz(file, graph, dp); // 遍历所有边,提取属性 auto edges_range = boost::edges(graph); for (auto e_it = edges_range.first; e_it != edges_range.second; ++e_it) { auto& edge = graph[*e_it]; // 这里可以根据实际属性名提取,或者修改动态映射类来记录所有属性名 // 示例:提取weight属性 boost::any weight_val = dp.get("weight", *e_it); if (!weight_val.empty()) { edge.edge_props["weight"] = boost::any_cast<double>(weight_val); } // 其他属性同理,若要自动枚举属性,需在动态映射中添加属性名追踪逻辑 } } catch (std::exception& e) { std::cerr << e.what() << std::endl; } catch (boost::exception& e) { std::cerr << boost::diagnostic_information(e) << std::endl; } }
关键提示
- Boost Graph的边属性键是
edge_descriptor类型,不是unsigned int,这是之前无法捕获边属性的核心原因。 - 增加字符串转数值的逻辑是因为
read_graphviz会将所有属性值以字符串形式传入,需要手动解析为数值类型。 - 使用
std::map存储动态属性,无需修改结构体即可适配任意数量的边属性。
内容的提问来源于stack exchange,提问作者dotdolfin
相关产品推荐
相关产品推荐

