グラフのインプリ ヒント
頂点や辺のデータにアクセスするにはメソッドを使う
汎用性のために頂点や辺が持つデータの型はObjectにしておきます。そうすると、実際に整数型が入っているときは以下のように使うことになります。
Integer i = (Integer)edge.target.data;
しかしこれでは、クラスの中身が丸見えで、後日リファクタリング等を行なう際の妨げにもなります。そのため各フィールドにアクセスするのにメソッドを使います。
Integer i = (Integer)edge.target().getData();
こうしておくと、後日たとえばtargetというフィールド名をtarget2変更したときに、コード中のtarget文字列を変更する必要はなくなります。
Node target() { return target2; }
という風にメソッドだけ変更すれば済むからです。
整数値と文字列の変換
Javaからユーザ入力された値を整数や実数として扱いたい場合が多々あります。スペース区切りの数値データを使う方法は以下のとおりです。
String input = "ここにデータが入る";
String[] data = input.split();
for (int i=0; i < data.length; i++)
{ int value = Integer.parseInt(data[i]); }
実数なら、Double.parseDouble(文字列); という関数があります。Boolean等も同じです。
データの隠蔽を心がける
Javaでコードを書くときは、そのコードを「ライブラリとして」後日使うことを意識すると便利です。例えば、グラフ構造のコードの中に、グラフ構造向けのアルゴリズムのコードが含まれることは望ましくありません。アルゴリズムはグラフ構造の外側からアクセスすべきです。
グラフ構造作成の好ましくない例
LinkedList edges = new LinkedList();
LinkedList nodes = new LinkedList();
Graph G = new Graph(nodes, edges);
こうしてしまうと、Graphクラス中で利用するデータ構造がLinkedListクラスであることがわかりますし、中身の入った別のLinkedListを渡すことも出来てしまいます。上記の実装は例えば以下のようにすべきです。
Class Graph {
LinkedList nodes, edges;
Graph() { nodes = new LinkedList(); edges = new LinkedList(); }
}
こうしてGraphクラスを作っていくと、例えばダイクストラ法で「頂点をvisitしたかどうか」をどう判定するか悩むことになります。Graphが使うNodeクラス中に、boolean visited; というフィールドを設けることは簡単ですが、これは他のほとんどの用途において無駄なフィールドになってしまいます。そのために、外部から全頂点や全辺の配列を得るメソッドを用意してあるのです。
Class Graph {
:
Node[] allNodes() {
Node[] ret = new Node[numberOfNodes()];
int i=0;
for(Node n = nodes.head(); n != null; n = n.next()) ret[i++] = n;
return ret;
}
}
この戻り値は配列である必要はなく、ハッシュテーブルや双方向リストのままでもOKです。ただし、
LinkedList allNodes() { return nodes; }
という実装は好ましくありません。これはGraphクラスの中身をそのまま渡してしまうため、せっかく隠蔽している意味がなくなってしまいます。