C++ Boost

Property Maps

抽象的な数学のグラフの世界と、それを使って解決される具体的な問題は、主に、頂点や辺に付加されたプロパティによって結ばれている。プロパティとは例えば、距離、容量、重み、色などだ。データ構造の実装の点では、プロパティを付加する方法は数多く有るが、グラフのアルゴリズムはプロパティの実装の詳細を取り扱うべきではない。 Property Map Concepts 節で定義されるプロパティマップを使えば、グラフからプロパティにジェネリックにアクセスできる。 BGL アルゴリズムはプロパティにアクセスするのにこのインタフェースを使っている。

Property Map Interface

プロパティマップのインタフェースでは、個々のプロパティが別々のプロパティマップオブジェクトを使ってアクセスされる事になっている。以下の例は、 Dijkstra 最短経路アルゴリズムの内部で使われる relax() 関数の実装である。この関数では、辺の重みと頂点の距離というプロパティにアクセスする必要が有る。 relax() はテンプレート関数として書かれているので、多くの異なる状況で使うことができる。関数への引数の内、 weightdistance はプロパティマップオブジェクトだ。一般に、BGL アルゴリズムは、関数が必要とする全てのプロパティに対して、プロパティマップを明示的に渡す。プロパティマップインタフェースはいくつかの関数を定義しているが、ここではその内 get()put() の2つを使う。 get() 関数はプロパティマップオブジェクト (例えば distance ) とキーオブジェクトをとる。距離プロパティの場合は、キーとして頂点オブジェクト uv を使う。 get() 関数はその頂点のプロパティ値を返す。

  template <class Edge, class Graph,
            class WeightPropertyMap, 
            class DistancePropertyMap>
  bool relax(Edge e, const Graph& g, 
             WeightPropertyMap weight, 
             DistancePropertyMap distance)
  {
    typedef typename graph_traits<Graph>::vertex_descriptor Vertex;
    Vertex u = source(e,g), v = target(e,g);
    if ( get(distance, u) + get(weight, e) < get(distance, v)) {
      put(distance, v, get(distance, u) + get(weight, e));
      return true;
    } else
      return false;
  }
関数 get() はプロパティ値のコピーを返す。プロパティマップインタフェースには at() という関数も有り、こちらはプロパティ値への参照 (マップが変更可能でなければ const 参照) を返す。

STL の iterator_traits クラス同様、プロパティマップ型に関連する型を推論するために使われる property_traits クラスが有る。関連する型とは、キーと値の型、プロパティマップカテゴリ (マップが読み取り可能か、書き込み可能か、両方か、を知るのに使う) である。 relax() 関数では、距離プロパティの型のローカル変数を宣言するために、 property_traits を使った。

  {
    typedef typename graph_traits<Graph>::vertex_descriptor Vertex;
    Vertex u = source(e,g), v = target(e,g);
    typename property_traits<DistancePropertyMap>::value_type
      du, dv; // local variables of the distance property type
    du = get(distance, u);
    dv = get(distance, v);
    if (du + get(weight, e) < dv) {
      put(distance, v, du + get(weight, e));
      return true;
    } else
      return false;
  }

グラフのプロパティには 2 種類有る。内部プロパティと外部プロパティだ。

内部プロパティ (Interior Properties)
とは、何らかの方法でグラフオブジェクトの「内部」に格納されたプロパティである。プロパティ値オブジェクトの寿命はグラフオブジェクトの寿命と同じだ。

外部プロパティ (Exterior Properties)
とは、グラフオブジェクトの「外部」に格納されたプロパティである。プロパティ値オブジェクトの寿命はグラフオブジェクトとは独立している。これは、一時的にしか必要でないプロパティに便利だ。例えば、 breadth_first_search() で使われる色プロパティのように、特定のアルゴリズムの間だけ必要な場合だ。外部プロパティを BGL のアルゴリズムに使う時は、外部プロパティ用のプロパティマップオブジェクトをアルゴリズムへの引数として渡す必要が有る。

Interior Properties

内部プロパティ記憶領域をサポートするグラフ型 (adjacency_list など) では、 PropertyGraph で定義されるインタフェースを使ってプロパティマップにアクセスできる。 get(Property, g) は、グラフからプロパティマップオブジェクトを取得する関数である。第 1 引数はどのプロパティにアクセスしたいかを示すプロパティ型だ。第 2 引数はグラフオブジェクトだ。グラフ型は、どのプロパティに (そしてそれによってどのタグに) アクセスできるかを記述しておかなければならない。グラフ型とマップされたプロパティによって、プロパティマップの型が決まる。プロパティマップの型を推論するジェネリックな方法を与える特性クラス property_map が定義されている。あるグラフ型の距離プロパティと重みプロパティのプロパティマップを得る方法を以下のコードで示す。

  property_map<Graph, vertex_distance_t>::type d
    = get(vertex_distance, g);

  property_map<Graph, edge_weight_t>::type w
    = get(edge_weight, g);

一般に BGL では、アルゴリズムに必要な全てのプロパティマップを、アルゴリズムに明示的に渡す事が要求される。例えば、 BGL の Dijkstra 最短経路アルゴリズムは、距離、重み、色、頂点 ID の 4 個のプロパティマップを要求する。

いくつか又は全てのプロパティがグラフの内部プロパティである場合が多いので、 Dijkstra のアルゴリズムは以下のようにして呼べる (あるグラフ g と始点 src が有るとする) 。

  dijkstra_shortest_paths(g, src,  
    distance_map(get(vertex_distance, g)).
    weight_map(get(edge_weight, g)).
    color_map(get(vertex_color, g)).
    vertex_index_map(get(vertex_index, g)));

全てのプロパティマップを指定するのは多少煩わしいので、 BGL はデフォルトで、いくつかのプロパティが内部プロパティで、グラフから get(Property, g) でアクセスできる事を仮定する。または、そのプロパティマップが内部的に使われるだけならば、アルゴリズムは配列を使って自分でプロパティマップを作り、グラフの頂点インデックスのマップを配列内のオフセットとして使う。以下に、名前付きパラメータ全てにデフォルトを使った dijkstra_shortest_paths アルゴリズムの呼び出しを示す。この呼び出しは先程の Dijkstra のアルゴリズムの呼び出しと等価である。

  dijkstra_shortest_paths(g, src);

次の質問は、「そもそもグラフオブジェクトにどうやって内部プロパティを付加するのか?」である。これは使うグラフクラスによる。 BGL の adjacency_list グラフクラスは、グラフの辺や頂点に任意の数のプロパティを格納できるように、プロパティのメカニズム (Internal Properties の節を参照) を使っている。

Exterior Properties

この節では、外部プロパティマップを構築する 2 つの方法を説明する。もっとも、グラフの外部プロパティマップを作る方法は無限にある。

第 1 の方法には、アダプタクラス random_access_iterator_property_map を使う。このクラスはランダムアクセスイテレータをラップし、プロパティマップを作る。ランダムアクセスイテレータはプロパティ値の範囲の先頭を指している必要が有り、範囲の長さはグラフの頂点か辺の数 (頂点と辺のどちらのプロパティマップかによる) と等しい必要がある。アダプタには、 ID プロパティマップも与える必要がある。頂点か辺の記述子をプロパティ値のオフセット (指定されたランダムアクセスイテレータからのオフセット) にマップするのに、この ID プロパティマップを使うからだ。 ID プロパティマップは、一般にグラフの内部プロパティマップである。以下の例では、配列に格納される容量プロパティと流れプロパティのための外部プロパティマップを作るために、 random_access_iterator_property_map をどう使うのかを示す。配列のインデックスは辺の ID だ。辺の ID はプロパティを使ってグラフに追加される。 ID の値は、辺がグラフに追加される時に与えられる。この例の完全なソースコードは examples/exterior_edge_properties.cpp に有る。 print_network() 関数は、流れと容量の値付きでグラフを出力する。

  typedef adjacency_list<vecS, vecS, bidirectionalS, 
    no_property, property<edge_index_t, std::size_t> > Graph;

  const int num_vertices = 9;
  Graph G(num_vertices);

  int capacity_array[] = { 10, 20, 20, 20, 40, 40, 20, 20, 20, 10 };
  int flow_array[] = { 8, 12, 12, 12, 12, 12, 16, 16, 16, 8 };

  // 各辺をグラフに追加し、各辺に ID を割り当てる。
  add_edge(0, 1, 0, G);
  // ...

  typedef boost::graph_traits<Graph>::edge_descriptor Edge;
  typedef property_map<Graph, edge_index_t>::type EdgeID_Map;
  EdgeID_Map edge_id = get(edge_index, G);

  boost::random_access_iterator_property_map
    <int*, int, int&, EdgeID_Map> 
      capacity(capacity_array, edge_id), 
      flow(flow_array, edge_id);

  print_network(G, capacity, flow);

第 2 の方法には、プロパティマップとしてポインタ型 (プロパティ値の配列へのポインタ) を使う。ポインタからのオフセットとして使うために、キーの型が整数である必要が有る。テンプレートパラメータ VertexList=vecS を使った adjacency_list クラスは頂点記述子として整数を使う (0 からグラフ内の頂点数までの値が振られる) 。よって、この頂点記述子はポインタプロパティマップのキーとして適当だ。 VertexListvecS でなければ、頂点記述子は整数ではないので、ポインタプロパティマップは使えない。代わりに、上で説明した ID プロパティと random_access_iterator_property_map を使う方法を使う必要が有る。適用される辺イテレータの定義によっては、 edge_list クラスも頂点記述子に整数型を使う場合が有る。 examples/bellman_ford.cpp の例では、 edge_list の頂点プロパティマップにポインタが使われている。

ポインタがプロパティマップとして使えるのは、ポインタ用のプロパティマップのインタフェースを実装する関数オーバロードや property_traits の特殊化版が、ヘッダ boost/property_map.hpp に存在するからだ。これらの関数の定義をここにリストアップする。

namespace boost {
  template <class T>
  struct property_traits<T*> {
    typedef T value_type;
    typedef ptrdiff_t key_type;
    typedef lvalue_property_map_tag category;
  };

  template <class T>
  void put(T* pa, std::ptrdiff_t key, const T& value) { pa[key] = value;  }

  template <class T>
  const T& get(const T* pa, std::ptrdiff_t key) { return pa[key]; }

  template <class T>
  const T& at(const T* pa, std::ptrdiff_t key) { return pa[key]; }

  template <class T>
  T& at(T* pa, std::ptrdiff_t key) { return pa[key]; }
}

以下の例では、グラフ内の各頂点に対応する都市名を格納するために配列を使い、 (breadth_first_search() の呼び出しに必要な) 頂点の色を格納するために std::vector を使う。 (begin() の呼び出しで得られる) std::vector のイテレータはポインタなので[訳注1]std::vector::iterator にもポインタプロパティマップの方法が使えるのである。この例の完全なソースコードは examples/city_visitor.cpp に有る。

// city_visitor の定義は省略...

int main(int,char*[])
{
  enum { SanJose, SanFran, LA, SanDiego, Fresno, LosVegas, Reno,
         Sacramento, SaltLake, Pheonix, N };

  // 頂点名プロパティの配列
  std::string names[] = { "San Jose", "San Francisco",  "San Jose",
                          "San Francisco", "Los Angeles", "San Diego", 
                          "Fresno", "Los Vegas", "Reno", "Sacramento",
                          "Salt Lake City", "Pheonix" };

  // 全ての都市間の連絡道路を指定する。
  typedef std::pair<int,int> E;
  E edge_array[] = { E(Sacramento, Reno), ... };

  // グラフの型を指定する。
  typedef adjacency_list<vecS, vecS, undirectedS> Graph;
  // edge_array の辺に基づいて、グラフオブジェクトを作る。
  Graph G(N, edge_array, edge_array + sizeof(edge_array)/sizeof(E));

  // DFS や BFS を使うには、頂点に「色を付ける」必要が有る。
  // ここでは外部プロパティの記憶領域として std::vector を使う。
  std::vector<default_color_type> colors(N);

  cout << "*** Depth First ***" << endl;
  depth_first_search(G, city_visitor(names), colors.begin());
  cout << endl;

  // 始点を取得。
  boost::graph_traits<Graph>::vertex_descriptor 
    s = vertex(SanJose, G);

  cout << "*** Breadth First ***" << endl;
  breadth_first_search(G, s, city_visitor(names), colors.begin());

  return 0;
}

Constructing an Exterior Property Map

独自の外部プロパティマップを実装するのはそれほど難しくない。必要なのは、自分のクラスのモデルにしたい property map concept に要求される関数をオーバロードする事だけだ。要するに、せいぜい put()get() をオーバロードして、operator[] を実装するぐらいだ。また、 property_traits で定義される全ての型について、自分のプロパティマップにネストした typedef を定義するか、自分のプロパティマップ型に対する property_traits の特殊化版を作る必要が有る。

random_access_iterator_property_map クラスの実装は、外部プロパティマップの作り方の良い例になっている。以下に random_access_iterator_property_map クラスの簡単化した実装、名付けて iterator_pa [訳注2]を示す。

まず、 iterator_map クラス自身の定義から始める。このアダプタクラスがとるテンプレートパラメータは、適用対象の Iterator 型と ID プロパティマップの 2 つだ。 ID プロパティマップの仕事は、キーとなるオブジェクト (一般に頂点か辺の記述子) を、整数のオフセットにマップする事だ。プロパティマップになるために、 iterator_map クラスには key_typevalue_typecategory の 3 つの typedef が必要だ。 IDMap のキーの型を知るために property_traits を使い、 Iterator の値の型を知るために iterator_traits を使う。 at() 関数を実装しようとしているので、カテゴリとして boost::lvalue_property_map_tag を選ぶ。

  template <class Iterator, class IDMap>
  class iterator_map
  {
  public:
    typedef typename boost::property_traits<IDMap>::key_type key_type; 
    typedef typename std::iterator_traits<Iterator>::value_type value_type;
    typedef boost::lvalue_property_map_tag category;

    iterator_map(Iterator i = Iterator(), 
                const IDMap& id = IDMap()) 
      : m_iter(i), m_id(id) { }
    Iterator m_iter;
    IDMap m_id;
  };

次に get()put()at() の 3 つのプロパティマップ関数を実装する。各関数で key オブジェクトは m_id プロパティマップを使って整数のオフセットに変換され、ランダムアクセスイテレータ m_iter からのオフセットに使われる。

  template <class Iter, class ID>
  typename std::iterator_traits<Iter>::value_type
  get(const iterator_map<Iter,ID>& i,
      typename boost::property_traits<ID>::key_type key)
  {
    return i.m_iter[i.m_id[key]];
  }
  template <class Iter, class ID>
  void
  put(const iterator_map<Iter,ID>& i,
      typename boost::property_traits<ID>::key_type key,
      const typename std::iterator_traits<Iter>::value_type& value)
  {
    i.m_iter[i.m_id[key]] = value;
  }
  template <class Iter, class ID>
  typename std::iterator_traits<Iter>::reference
  at(const iterator_map<Iter,ID>& i,
      typename boost::property_traits<ID>::key_type key)
  {
    return i.m_iter[i.m_id[key]];
  }

以上だ。これで iterator_map クラスは完成であり、前節の random_access_iterator_property_map と全く同じように使える。



[訳注1] std::vector のイテレータがポインタである事は標準では保証されていない。代表的な例外は VC.NET である。
[訳注2] iterator_map の間違いと思われる。


Copyright © 2000-2001 Jeremy Siek, Indiana University (jsiek@osl.iu.edu)

Japanese Translation Copyright © 2003 Hiroshi Ichikawa
オリジナルの、及びこの著作権表示が全ての複製の中に現れる限り、この文書の複製、利用、変更、販売そして配布を認める。このドキュメントは「あるがまま」に提供されており、いかなる明示的、暗黙的保証も行わない。また、いかなる目的に対しても、その利用が適していることを関知しない。

このドキュメントの対象: Boost Version 1.29.0
最新版ドキュメント (英語)