2011-12-20
Boost.Graphの概要
これはBoost Advent Calendar 2011の20日目の記事です。
この記事の前半の方はBoost.Graphを使ったことがない人をターゲットとしており、なんとなくBoost.Graphの雰囲気を知ってもらうことを目的としています。
後半の方はBoost.Graphを使う上ではまったく知っておく必要がないことを書いてあり、私がなんとなく気になって調べたことをかいてあります。
Boost.Graphとは?
頂点(vertex)と辺(edge)の集合からなるグラフのデータを扱ったり、グラフのアルゴリズムが実装されているライブラリです。
たとえば、3個のvertexからなり、vertex 0 -> 1、0 -> 2、1 -> 2の3個のエッジを持つ有向グラフのデータをBoost.Graphを使って作るには以下のようなコードになります。
#include <vector> #include <boost/graph/adjacency_list.hpp> int main() { std::vector<std::pair<int, int> > edge_list = {{0, 1}, {0, 2}, {1, 2}}; typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, boost::no_property, boost::no_property, boost::no_property> Graph; Graph graph(edge_list.begin(), edge_list.end(), 3); // コンストラクタの引数は、エッジイテレータの最初, 最後, vertex数の順 return 0; }
これで変数graphには、以下のようなグラフ構造ができます。
vertexにプロパティを付ける
各頂点(vertex)には、それに紐付いたデータ(プロパティ)を付けることができます(エッジやグラフ自体にもプロパティを付けることは可能です)。たとえば、各vertexにstd::stringとintのデータを持たせたい場合は、
- プロパティを区別するためのタグであるenumを定義する(今回はvertex_vname_tにstd::string、vertex_vid_tにintとする。enumの命名規則はルールがあるので注意)
- BOOST_INSTALL_PROPERTYマクロでプロパティを設定する。
- boost::propertyを入れ子状に使って、必要なvertexプロパティを定義
という感じです。詳しくは以下のソースです
#include <vector> #include <boost/graph/adjacency_list.hpp> enum vertex_vname_t { vertex_vname }; enum vertex_vid_t { vertex_vid }; namespace boost { BOOST_INSTALL_PROPERTY(vertex, vname); BOOST_INSTALL_PROPERTY(vertex, vid); } int main() { std::vector<std::pair<int, int> > edge_list = {{0, 1}, {0, 2}, {1, 2}}; // vertexのプロパティを定義。 // std::string(識別のタグとしてvertex_vname_t)とint(タグはvertex_vid_t)をvertexのデータにする typedef boost::property<vertex_vname_t, std::string, boost::property<vertex_vid_t, int> > VertexProperty; typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, VertexProperty, // VertexPropertyにする boost::no_property, boost::no_property> Graph; Graph graph(edge_list.begin(), edge_list.end(), 3); return 0; }
必要に応じて、typedef ... VertexPropertyの部分に新しいboost::propertyを追加することで柔軟なプロパティを扱えるようになっているようです。もし3個のプロパティを使うなら以下のような感じになります。
typedef boost::property<vertex_vname_t, std::string, boost::property<vertex_vid_t, int, boost::property<vertex_hoge_t, double> > > VertexProperty;
さて、実際に値を設定するには、put関数を使います(引数は、タグ, グラフオブジェクト, vertexの番号, valueの順)。具体的な使い方は以下のソースのような感じです
boost::put(vertex_vname_t(), graph, 0, "abc"); // タグを指定(vertex_vname_t)して値を設定する boost::put(vertex_vname_t(), graph, 1, "def"); boost::put(vertex_vname_t(), graph, 2, "ghi"); boost::put(vertex_vid_t(), graph, 0, 100); boost::put(vertex_vid_t(), graph, 1, 200); boost::put(vertex_vid_t(), graph, 2, 500);
このように書くと、以下のようにグラフに値が設定されます。
vertexのプロパティを取得するにはget関数を使います。put関数と同様にタグを指定します。たとえば以下のような感じです。
std::string a = boost::get(vertex_vname_t(), graph, 1); int b = boost::get(vertex_vid_t(), graph, 2);
これで、変数aには"def"、bには500が入ります。
実際はどんなクラスでプロパティが保存されているのか?
Boost.Graphを使う上ではまったく関係ありませんが、実際にはどんなクラスでプロパティが保存されているか確認してみます。
これはboostのpendingディレクトリにあるproperty.hppを見れば分かります。たとえば上記で使ったプロパティ
boost::property<vertex_vname_t, std::string,
boost::property<vertex_vid_t, int> >
が以下のように具体的にはなります。
struct boost::no_property { }; template <> struct boost::property<vertex_vid_t, int, boost::no_property> : public boost::no_property { typedef boost::no_property next_type; typedef vertex_vid_t tag_type; typedef int value_type; int m_value; }; template <> struct boost::property<vertex_vname_t, std::string, boost::property<vertex_vid_t, int> > : public boost::property<vertex_vid_t, int> { typedef boost::property<vertex_vid_t, int> next_type; typedef vertex_vname_t tag_type; typedef std::string value_type; std::string m_value; };
つまり、自分よりも一つ子供のデータ(上で定義したVertexPropertyを見ればわかるように、std::stringの一つ子供はint、intのさらに子供は無しのように)が入ったpropertyクラスを次々と継承していくようになっています(最初はno_propertyから始まりますが)。
getの戻り値の型はどう決まる?
さらに脱線しまして、Boost.Graphを使う上では全然全く知る必要がありませんが、ちょっと気になることがあるので調べてみました。
vertexについているプロパティを取得するためには、
std::string a = boost::get(vertex_vname_t(), graph, 1); int b = boost::get(vertex_vid_t(), graph, 2);
のようなコードを書きました。このコードを良く見てみますと、引数に渡しているのはvertex_vname_t()等のenumを渡しているだけなのに、きちんと期待した戻り値の型で関数が呼ばれているようです。どうやってboost::getの戻り値の型を決めているのでしょうか。
コードを追ってみますと、同じくproperty.hppにあるpropety_value構造体で戻り値の型を求めているようです。property_valueは以下のような構造体になっています(property.hppから抜粋)。
template <class PropertyList, class Tag> struct property_value { typedef typename detail::build_property_tag_value_alist<PropertyList>::type AList; typedef typename detail::extract_value<AList,Tag>::type type; };
どうやら、
// 他の部分は省略...
boost::property_value<VertexProperty, vertex_vname_t>::type ret1;
boost::property_value<VertexProperty, vertex_vid_t>::type ret2;
のように書くと、ret1やret2の型をタグによって決めてくれ、ret1の型はstd::string、ret2の型はintにしてくれるようです。
もう少しproperty_valueを追ってみます。build_property_tag_value_alist<PropertyList>の部分で、PropertyListがVertexPropertyの場合の展開について確認しますと以下のようになるようです。
template <> struct build_property_tag_value_alist<no_property> { typedef no_property type; }; template<> struct build_property_tag_value_alist<boost::property<vertex_vid_t, int> > { typedef no_property NextProperty; typedef int Value; typedef vertex_vid_t Tag; typedef uild_property_tag_value_alist<no_property>::value Next; typedef std::pair<std::pair<vertex_vid_t, int>, build_property_tag_value_alist<no_property>::type> type; }; template<> struct build_property_tag_value_alist<boost::property<vertex_vname_t, std::string, boost::property<vertex_vid_t, int> > { typedef boost::property<vertex_vid_t, int> NextProperty; typedef std::string Value; typedef vertex_vname_t Tag; typedef build_property_tag_value_alist<boost::property<vertex_vid_t, int> >::type Next; typedef std::pair<std::pair<vertex_vname_t, std::string>, build_property_tag_value_alist<boost::property<vertex_vid_t, int> >::type> type; };
自分よりも子供の子供のタグ(vertex_vname_tの子供はvertex_vid_t、そのさらに子供は無し)についてbuild_property_tag_value_alistを読んでいるようでした。
したがってbuild_property_tag_value_alist<PropertyList>::typeがPropetyListがVertexPropertyの場合の最終的な型は以下のようになります
std::pair<std::pair<vertex_vname_t, std::string>,
std::pair<std::pair<vertex_vid_t, int>,
no_property> > >
property_valueのもう一つのtypedefのextract_value<AList,Tag>::typeで最終的にそのタグについた型を決めています。この部分は以下のようになっています(property.hppから抜粋)。
template <class Value, class Tag1, class Tag2, class Rest>
struct extract_value< std::pair<std::pair<Tag1,Value>,Rest>, Tag2> {
typedef typename extract_value<Rest,Tag2>::type type;
};
template <class Value, class Tag, class Rest>
struct extract_value< std::pair<std::pair<Tag,Value>,Rest>, Tag> {
typedef Value type;
};
探そうとしているタグが
- pair<pair<タグがここと一致なら, Value, Rest> →そのときのValueがタグに関連する型
- pair<pair<タグがここと不一致なら, Value, Rest> →さらに深くもぐって探す
のようにやっているようです。
これで、getの戻り値の型が与えられたタグ(enum)から決まるようです。
まとめ
前半では、Boost.Graphの簡単な説明、後半では脱線して、プロパティの中身の話をかきました。
今度機会があれば、Boost.Graphのvisitorの話もしたいと思っています。
明日担当の@eagle_raptor さんもGraphの予定なようなので、私のよりも訳にたつ話が聞けると思います。
- 75 http://partake.in/events/597a0fc3-0e3a-47a3-8fc3-4f32ad846a3d
- 10 http://d.hatena.ne.jp/eagle_raptor/20111221/1324478088
- 10 http://t.co/czEQCcBm
- 9 http://homepage1.nifty.com/h-fuji/tosyo/tosyo_form_rakuten.htm
- 8 http://t.co/jxv4VPdq
- 7 http://t.co/0EUQAxHY
- 7 http://www.google.co.jp/url?sa=t&rct=j&q=楽天 API 書籍 xml xpath&source=web&cd=6&ved=0CEYQFjAF&url=http://d.hatena.ne.jp/futada/20100210/1265786012&ei=31AeT_3vFq7ImQX15IGkDg&usg=AFQjC
- 7 http://www.google.co.jp/url?sa=t&rct=j&q=isbn 検索 必要な情報&source=web&cd=5&ved=0CD0QFjAE&url=http://d.hatena.ne.jp/futada/20100210/1265786012&ei=8Mj3TobDKqihiAe_g7nDAQ&usg=AFQjCNHqGAlTsDI
- 6 http://www.google.co.jp/url?sa=t&rct=j&q=エクセル ISBN&source=web&cd=4&ved=0CDsQFjAD&url=http://d.hatena.ne.jp/futada/20100210/1265786012&ei=QNn1Tq7HEJCImQXRy6WSAg&usg=AFQjCNHqGAlTsDI6r1DAoP0Wk4-xD6AEKg
- 6 http://www.google.co.jp/url?sa=t&rct=j&q=prime note rigel dc&source=web&cd=9&ved=0CGgQFjAI&url=http://d.hatena.ne.jp/futada/20100124/1264357275&ei=FG4dT4-sOqzRmAXUwLXRBg&usg=AFQjCNFeSfy8QWeoawXfxR14KP-O6zAalA&sig2=FYe-zyaSSgnAGaN7ucz5Xg


「vertex 0 -> 1、0 -> 2、1 -> 2の3個のエッジを持つ有効グラフのデータををBoost.Graph」
有効 → 有向
をを → を
std::vector<std::pair<int, int> > edge_list
= {std::pair<int, int>(0, 1), std::pair<int, int>(0, 2), std::pair<int, int>(1, 2)};
GCCの新しめのバージョンを使っているのであれば、Uniform Initializationが使えるので、pairも波カッコで書けます。
std::vector<std::pair<int, int> > edge_list = {{0, 1}, {0, 2}, {1, 2}};
「もし3このプロパティを使うなら」
3こ → 3個
ご指摘いただいた点修正しました。ちょうど21日の日記でUniform Initializationを書いているんですね
template <>
struct boost::property<vertex_vid_t, int,
boost::no_property>
: public boost::no_property
{
typedef boost::no_property next_type;
typedef vertex_vid_t tag_type;
typedef int value_type;
int m_value;
}