You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 19:12:11