C++ Boost

Six Degrees of Kevin Bacon

人気のあるゲーム“Kevin Bacon の6次数”においてグラフ理論の愉快な応用 を思いついた。ゲームのアイデアは俳優と Kevin Bacon を、映画で共演した 俳優の道を通して結ぶというもので、それを6段階未満で行うものである。 例えば、Theodore Hesburgh (ノートルダム大学の名誉会長) は映画“Ruby”中 で俳優 Gerry Becker と共演し、彼は映画“Sleepers”で Kevin Bacon と 共演している。なぜ Kevin Baconなのだろうか?いくつかの理由のために ゲームを考案した三人の学生 Mike Ginelli、Graig Fass、そして Brian Turtle は Kevin Bacon を芸能界の中心であると決定した。Kevin Bacon ゲームは “世界の至るところで長年数学者の民俗学の一部だった” Erdös Number と実によく似ている。

“Kavin Bacon の6次数”ゲームは実際にはグラフの問題である。もし各俳優を 頂点に割り当て、二人の俳優が映画で共演しているなら彼らの間に辺を付加 すれば、このゲームのためのデータを表すグラフになる。それから Kavin Bacon への俳優の道を見つける問題は、従来のグラフの問題になる。つまり二つの頂点間 の path を見つける事である。6段階より短い道を見つけたいので、理論上 頂点間の shortest path を見つける事になるだろう。この問題に Dijkstra の最短経路のアルゴリズムを適用する事を考えるかも知れないが、 Dijkstra のアルゴリズムは各辺が関連した長さ (または重み) を持っていて、 目的が最短の累積長さを発見するというような状況に向いているため過剰であろう。 辺の数の見地から最短経路を見つけようと思うだけなので、幅優先探索 アルゴリズムが (Dijkstra のアルゴリズムより少ない時間で) 問題を解くだろう。

Input File and Graph Setup

この例のために全ての映画と全ての俳優より遙かに小さいグラフを使う。 この例の完全なソース・コードは examples/kevin-bacon.cpp にある。同じ映画で出演した俳優のペアのリストを含むファイル kevin_bacon.dat を提供した。ファイルの各行は俳優の名前、映画、そして 映画で出演した他の俳優を含む。“|”文字はセパレータとして使われる。ここに 抜粋がある。

Patrick Stewart|Prince of Egypt, The (1998)|Steve Martin

最初の仕事はファイルを読み込み、それからグラフを作成する事だろう。 ファイルを入力するために std::ifstream を使う。

  std::ifstream datafile("./kevin_bacon.dat");
  if (!datafile) {
    cerr << "No ./kevin_bacon.dat file" << endl;
    return -1;
  }

俳優の名前をグラフ中の頂点に、そして映画の名前を辺に結びつけたい。 これらの頂点と辺のプロパティのグラフへの付加を具体的にするために property クラスを使う。無向グラフを使う事を選択した。というのは 映画中で共演した俳優の関係は対称的だからである。

  using namespace boost;
  typedef adjacency_list<vecS, vecS, undirectedS, 
    property<vertex_name_t, string>,
    property<edge_name_t, string, property<edge_weight_t, int> >
  > Graph;

プロパティにアクセスするために、グラフからプロパティ・マップ・オブジェクトを 得る必要がある。次のコードはこれがどのように行われたか示す。

  boost::property_map<Graph, vertex_name_t>::type actor_name = get(vertex_name, g);
  boost::property_map<Graph, edge_name_t>::type connecting_movie = get(edge_name, g);
  boost::property_map<Graph, edge_weight_t>::type weight = get(edge_weight, g);

次に各行をトークンのリストに構文分析しながらファイルを通してループを開始 できる。stringtok は古い strtok 標準 C 関数の C++ バージョン である。

  std::string line;
  while (std::getline(datafile,line)) {
    std::list<std::string> line_toks;
    boost::stringtok(line_toks, line, "|");
    ...
  }

入力の各行は辺に接続した頂点として二人の俳優を伴いグラフの辺に対応し、 映画の名前は辺に結びついたプロパティとなるだろう。 入力のこの書式からグラフを作成する際の一つの問題は、辺が俳優の名前のペアで 指定されている事である。グラフに頂点を追加する時に、同じ俳優 (違う辺の上で) が後で出現してもグラフ中の正しい頂点につなげられるように、俳優の名前から それらの頂点へのマップを作成する必要があるだろう。 std::map がこのマッピングを実装するのに使える。

  typedef graph_traits<Graph>::vertex_descriptor Vertex;
  typedef std::map<string, Vertex> NameVertexMap;
  NameVertexMap actors;

一番目のトークンは俳優の名前になるだろう。もし俳優が既に俳優のマップ中にない なら、頂点をグラフに付加し、頂点の名前プロパティを置き、頂点記述子をマップ中に 記録するだろう。もし俳優が既にグラフ中にあれば、頂点記述子をマップの pos イテレータから取ってくるだろう。

  NameVertexMap::iterator pos; 
  bool inserted;
  Vertex u, v;

  std::list<std::string>::iterator i = line_toks.begin();

  boost::tie(pos, inserted) = actors.insert(make_pair(*i, Vertex()));
  if (inserted) {
    u = add_vertex(g);
    actor_name[u] = *i;
    pos->second = u;
  } else
    u = pos->second;
  ++i;

二番目のトークンは映画の名前で、三番目のトークンは他の俳優である。 二番目の俳優をグラフ中に挿入するために上述と同じ手法を使う。

  std::string movie_name = *i++;
      
  boost::tie(pos, inserted) = actors.insert(make_pair(*i, Vertex()));
  if (inserted) {
    v = add_vertex(g);
    actor_name[v] = *i;
    pos->second = v;
  } else
    v = pos->second;

最後の段階は二人の俳優を結ぶ辺を付加し、それを結ぶ映画の名前を記録する事で ある。 さらに辺の重みを 1 と置く。adjacency_listEdgeList 型 のために setS を選択したので、入力中のどの多重辺もグラフ中に挿入 されないだろう。

  Edge e;
  boost::tie(e, inserted) = add_edge(u, v, g);
  if (inserted) {
    connecting_movie[e] = movie_name;
    weight[e] = 1;
  }

Applying Breadth-First Search

breadth_first_search() アルゴリズムの文書は色と頂点IDをプロパティ・マップに供給する必要が あるだろうという事を示している。それに加え、先行点と距離のためのプロパティ・ マップを同様に必要とするため、これらを記録するためにビジタを使うだろう。 距離は、各俳優からアルゴリズムによって計算された Kevin Bacon までの最短距離 になるだろう。この距離はさらに、どれくらいの出版が数学者を Paul Erdos から 分けたかに従い数学者を分類するのに使われた“Erdos Number”を記念して“Bacon Number”と呼ばれた。bacon 番号を格納するために vector を使い、BFS アルゴリズム によって必要とされるカラー・プロパティをしまうために別の vector を使うだろう。 predecessor vector は各俳優から Kevin までの最短距離を記録するのに 使われるだろう。各頂点のために predecessor vector が Kevin Bacon への最短経路の上の次の頂点へのポインタを含むだろう。

  std::vector<int> bacon_number(num_vertices(g));
  std::vector<int> color(num_vertices(g));
  std::vector<Vertex> predecessor(num_vertices(g));

さらに BFS アルゴリズムは入力として始点を必要とする。もちろんこれは Kavin Bacon だろう。グラフ、始点、プロパティ・マップ、そして先行点と距離の レコーダから構成されたビジタを渡して、今 BGL BFS を呼ぶ。 先行点を記録するために predecessor_recorder を、そして距離を記録するために distance_recorder を使う。

    Vertex src = actors["Kevin Bacon"];
    
    breadth_first_search
      (g, src, 
       visitor(make_bfs_visitor
	       (make_list(record_predecessors(&predecessor[0], 
					      on_examine_edge()),
			  record_distances(&bacon_number[0],
					   on_examine_edge()))))
       );

単にグラフ中の全ての頂点を通してループし、それらを bacon_number vector 中に添え字を付けるために用いる事によって、各俳優のための bacon 番号を出力できる。

    graph_traits<Graph>::vertex_iterator i, end;
    for (boost::tie(i, end) = vertices(g); i != end; ++i)
      std::cout << actor_name[*i] << "'s bacon number is " 
                << bacon_number[*i] << std::endl;

bacon_number のような vector または配列が添え字として頂点記述子 オブジェクトを必ずしも使うことができるとは限らないことに注意しなさい。これは VertexList=vecS の状態の adjacency_list クラスに有効で、 他の状態の adjacency_list は有効でない。 頂点に基づき添え字を付ける、より一般的な方法は iterator_property_map と連携して ID プロパティ・マップ (vertex_index_t) を使う 事である。

ここれにプログラムの出力からの抜粋がある。
[訳注: excepts は excerpts の間違いであろうと判断しそう訳した]

William Shatner was in Loaded Weapon 1 (1993) with Denise Richards
Denise Richards was in Wild Things (1998) with Kevin Bacon
Patrick Stewart was in Prince of Egypt, The (1998) with Steve Martin
...
William Shatner's bacon number is 2
Denise Richards's bacon number is 1
Kevin Bacon's bacon number is 0
Patrick Stewart's bacon number is 2
Steve Martin's bacon number is 1    
...


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

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

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