Boost.Graph Graphvizの情報をユーザー定義型に読み込む

Boost.Graphのadjacency_listクラスには、Bundleプロパティという仕組みを使って、頂点や辺の情報を、任意のユーザー定義型に保持できます。

そのような作りになっているグラフオブジェクトに、Graphvizのデータを読み込むサンプルコードを、以下の示します。

読み込むGraphvizデータ(graph.dot)

digraph G {
0 [name="A", pos="(0,0)", id=1];
1 [name="B", pos="(-3,3)"];
2 [name="C", pos="(3,3)"];
3 [name="D", pos="(0,6)"];

0->1;
0->2;
1->3;
2->3;
}

このデータを読み込むコード:

#define BOOST_GRAPH_USE_SPIRIT_PARSER // for header only
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/optional.hpp>
#include <fstream>
#include <iostream>

struct Point {
    int x = 0;
    int y = 0;
};

std::ostream& operator<<(std::ostream& os, const Point& p)
{
    return os << '(' << p.x << ',' << p.y << ')';
}

std::istream& operator>>(std::istream& is, Point& p)
{
    if (is.get() != '(') {
        is.setstate(std::ios_base::failbit);
        return is;
    }

    is >> p.x;
    if (!is) {
        is.setstate(std::ios_base::failbit);
        return is;
    }

    if (is.get() != ',') {
        is.setstate(std::ios_base::failbit);
        return is;
    }

    is >> p.y;
    if (!is) {
        is.setstate(std::ios_base::failbit);
        return is;
    }

    if (is.get() != ')') {
        is.setstate(std::ios_base::failbit);
    }
    return is;
}

namespace boost {
    template <class T>
    std::ostream& operator<<(std::ostream& os, const boost::optional<T>& x)
    {
        if (x) {
            os << x.get();
        }
        else {
            os << "";
        }
        return os;
    }

    template <class T>
    std::istream& operator>>(std::istream& is, boost::optional<T>& x)
    {
        T result;
        if (is >> result) {
            x = result;
        }
        return is;
    }
}

struct Vertex {
    int nodeId;
    std::string name;
    boost::optional<int> id;
    Point pos;
};

using Graph = boost::adjacency_list<
    boost::listS,
    boost::vecS,
    boost::directedS,
    Vertex
>;

void load(Graph& g)
{
    boost::dynamic_properties dp;
    dp.property("node_id", boost::get(&Vertex::nodeId, g));
    dp.property("name", boost::get(&Vertex::name, g));
    dp.property("id", boost::get(&Vertex::id, g));
    dp.property("pos", boost::get(&Vertex::pos, g));

    std::ifstream file("graph.dot");
    if (!boost::read_graphviz(file, g, dp)) {
        throw std::runtime_error("can't load graph.dot file.");
    }
}

int main()
{
    Graph g;
    load(g);

    std::cout << "name : " << g[0].name << std::endl;
    std::cout << "pos : " << g[0].pos << std::endl;
}

出力:

name : A
pos : (0,0)

Bundleプロパティに設定されたユーザー定義型と、Graphvizの情報をマッピングするには、boost::dynamic_propertiesクラスを使用します。ここに、属性名と、それに相当するBundleプロパティのメンバ変数を列挙していきます。

ここでは、Graphvizの頂点データに含まれる、以下の情報を読み込んでいます:

  • ノードID (頂点ID)
    • これは、0, 1, 2, 3といった頂点のIDにあたります。"node_id"という名前で、このIDを読み込めます。このIDがいらない場合は、読み込まなくてもいいです。
  • 頂点の名前
    • nameという名前に設定された属性は、std::string型のメンバ変数に読み込んでいます。
  • ID
    • 頂点に、ノードID以外の任意のIDを持たせています。これは省略可能な要素とし、boost::optional<int>型に読み込んでいます。そのための入出力ストリーム演算子を用意してあります。
  • 頂点の表示位置
    • 表示位置は、ユーザー定義型のPoint型に読み込んでいます。これは、(x,y)という形式で書いています。

頂点や辺の各属性を読み込む際には、その型の入力ストリーム演算子が呼ばれます。ここで、任意のフォーマットになっているユーザー定義型のデータを読み込めます。

boost::optionalの読み込みについては、公式の入出力ストリームとはフォーマットが異なることに注意です。ここでは、その属性を読み込めなかった場合に無効な状態、読み込めた場合に有効な状態としています。boost::optional公式のフォーマットを使用する場合には、<boost/optional/optional_io.hpp>をインクルードしてください。

optionalに関連して。属性が記載されていない場合には、operator>>は呼ばれず、その型のデフォルト値が設定されます。