最終課題
ステップ0
以下のステップを実行する前に,概要の 課題のデータから必要なデータをダウンロードしておいてください.
ステップ1
1-A. 問題説明
コマンドライン引数で与えられたデータ(複数可能)を読み,以下の分析結果を出力してください.
- 収容可能台数(自転車)の最大値
- 収容可能台数(自転車)の最小値
- 収容可能台数(自転車)の合計
- 収容可能台数(自転車)の平均
なお,データの各桁は,次の項目があり,各行には,各桁の値が記入されています.
該当しない項目は空白であったり,駐輪代が数値ではなく,無料のように書かれている場合もありますので,注意してください.
- 施設名
- 公営・民営
- 公共交通機関
- 最寄駅
- 問合せ先
- 所在地
- 収容可能台数(自転車)
- 収容可能台数(バイク)
- 収容可能台数(原付)
- 収容可能台数(自動二輪)
- 収容可能台数(自転車+バイク)
- 駐輪代(自転車)/日
- 駐輪代(原付)/日
- 駐輪代(自動二輪)/日
- 駐輪代(小型自動二輪(125cc以下))/日
- 料金(詳細説明)
- 無料時間
- 月極の有無
- 営業開始
- 営業終了
- 備考
- 経度
- 緯度
1-B. 実行例
$ java ParkingAnalyzer1 parking1.csv
max: 1600, min: 5, total: 6060, average: 303.000000
$ java ParkingAnalyzer1 parking1.csv parking2.csv
max: 1600, min: 1, total: 17370, average: 190.879121
$ java ParkingAnalyzer1 # 何も引数が与えられない場合 N/A (not applicable)を出力する.
max: N/A, min: N/A, total: N/A, average: N/A
- 一つのファイルだけでなく,複数のファイルが指定されたとしても,正常に処理してください.
- ファイルが何も指定されなかった場合は,
N/A(not applicable) を出力するようにしてください. - 違うフォーマットのファイルが指定されることは考慮する必要はありません.
- 駐輪代などが空白の場合は,無視してください.
1-C. ヒント
1-D. 評価項目
- 概要に示した評価項目
- データを変更しても,例外なく実行結果を出力できるか.
- 引数が与えられなかった場合でも,適切な結果が出力できるか.
ステップ2
2-A. 問題説明
コマンドライン引数で与えられたデータ(複数可能)を読み,以下の分析結果を出力してください.
- 収容可能台数(自転車)の最大値
- 収容可能台数(自転車)の最小値
- 収容可能台数(自転車)の合計
- 収容可能台数(自転車)の平均
- 駐輪代(自転車)/日の最大値
- 駐輪代(自転車)/日の最小値
- 駐輪代(自転車)/日の合計
- 駐輪代(自転車)/日の平均
2-A-a. 満たすべき要件
ただし,以下の2つの要件を満たすようにプログラムを作成してください.
ParkingAnalyzer2.javaのフィールドが4つ以下になるようにしてください.ParkingAnalyzer2.javaのそれぞれのメソッドにおいても,宣言する変数が4つ以下になるようにしてください.
2-B. 実行例
$ java ParkingAnalyzer2 parking1.csv
収容可能台数(自転車) max: 1600 min: 5, total: 6060, average: 303.000000
駐輪代(自転車)/日 max: 400 min: 100, total: 2650, average: 176.666667
$ java ParkingAnalyzer2 parking1.csv parking3.csv
収容可能台数(自転車) max: 1721 min: 1, total: 15873, average: 244.200000
駐輪代(自転車)/日 max: 600 min: 100, total: 9200, average: 204.444444
$ java ParkingAnalyzer2 # 何も引数が与えられない場合 N/A (not applicable)を出力する.
収容可能台数(自転車) max: N/A, min: N/A, total: N/A, average: N/A
駐輪代(自転車)/日 max: N/A, min: N/A, total: N/A, average: N/A
2-C. ヒント
2-C-a. 満たすべき要件のために
- 満たすべき要件を果たすため,最大値,最小値,合計,平均をまとめる型を導入すると良いでしょう.
Stats.javaを定義し,フィールドにmax,min,sum,countを宣言します.void update(Integer value)メソッドでは,max,min,sum,countを更新します.Double average()メソッドは,その時点での平均を返します(sum,countから求められます).String型を返すstringメソッドを用意しておくと良いでしょう.countが0ならばN/Aを含む一行を返し,そうでなければ,適切な数値を入れた一行を返すようにすると良いでしょう.String.formatでprintfのように文字列を整形できます.
ParkingAnalyzer2では,データを読むときには,Statsのupdateメソッドを呼び,必要な値を渡しましょう.
public class Stats{
// ... 途中省略
String string(){
if(count == 0){
return "max: N/A, min: N/A, total: N/A, average: N/A";
}
return String.format("適切な文字列に置き換える");
}
}
2-C-b. String.format メソッド.
String name = "Tamada";
String hello = String.format("Hello, %s", name);
// => hello には,"Hello, Tamada" が代入される.
2-D. 評価項目
- 概要に示した評価項目
- データを変更しても,例外なく実行結果を出力できるか.
- 独自の型を作成できているか.
- 引数が与えられなかった場合でも,適切な結果が出力できるか.
ステップ3
3-A. 問題説明
与えられたデータを読み,指定された最寄駅から近い駐輪場を調べ, それらの収容可能台数(自転車),駐輪代の最大値,最小値,平均値,合計を出力してください. 各行の最寄駅の項目が与えられた文字列と一致すると,最寄駅から近いと判断してください.
上記の出力後,指定された最寄駅から近い駐輪場の一覧を出力してください. 一覧の出力には,次の項目のみを出力するようにしてください.
- 駐輪場の名前
- 最寄駅
- 収容可能台数(自転車)
- 駐輪代(自転車)/日
- 緯度
- 経度
なお,自転車を止めるのに一番安い駐輪場にマーク(*)をつけてください.
プログラムには,2つ以上の引数が渡されます. 第1引数には,最寄駅を表す文字列,第2引数以降は駐輪場のcsvファイルです.
3-B. 実行例
$ java ParkingAnalyzer3 北大路 parking1.csv
収容可能台数(自転車) max: N/A, min: N/A, total: N/A, average: N/A
駐輪代(自転車)/日 max: N/A, min: N/A, total: N/A, average: N/A
$ java ParkingAnalyzer3 北大路 parking1.csv parking2.csv parking3.csv
収容可能台数(自転車) max: 784 min: 125, total: 1069, average: 356.333333
駐輪代(自転車)/日 max: 200 min: 100, total: 300, average: 150.000000
北大路駅東駐車場,北大路,,,135.7588742,35.04504145
キタオオジタウン駐輪場,北大路,784,,135.7588037,35.04438447
今宮駐輪場,北大路,160,200,135.7580788,35.0465088
* ブーブーパーク北大路駅北駐輪場,北大路,125,100,135.7580786,35.0465132
$ java ParkingAnalyzer3 北大路
収容可能台数(自転車) max: N/A, min: N/A, total: N/A, average: N/A
駐輪代(自転車)/日 max: N/A, min: N/A, total: N/A, average: N/A
$ java ParkingAnalyzer3 parking1.csv # 最寄駅が parking1.csv として解釈される.
収容可能台数(自転車) max: N/A, min: N/A, total: N/A, average: N/A
駐輪代(自転車)/日 max: N/A, min: N/A, total: N/A, average: N/A
$ java ParkingAnalyzer3 # 何も指定しない場合はエラーメッセージ.
最寄駅が指定されていません.
3-C. ヒント
Integer型の値が一致しているかは,==ではなく,Objects.equalsを利用してください.- 数値リテラル(
123などの数値)との比較は==で想定通りの結果となりますが,変数同士は参照の一致を確認する演算子として扱われます.
- 数値リテラル(
Parking型を用意しておくと良いでしょう.void parse(String line)メソッドを用意しましょう.- 引数に与えられた
lineをコンマ(,)で分割し,適切なフィールドに代入しましょう.- その際,
無料のような文字列を見つけたら,0に置き換えるなどすると良いでしょう.
- その際,
- 引数に与えられた
String string()メソッドを用意しましょう.- 3-A. 問題説明に書かれている必要項目を文字列として返しましょう.
String.formatメソッドを利用すると良いでしょう.
- 3-A. 問題説明に書かれている必要項目を文字列として返しましょう.
3-D. 評価項目
- 概要に示した評価項目
- データを変更しても,例外なく実行結果を出力できるか.
- 与えられた引数が不正だった場合に,エラーを出力できるか.
ステップ4
4-A. 問題説明
与えられたオプションを元に,与えられたデータを読み,適切な処理を行ってください.
なお,駐輪場のリストを出力するときに,出力結果が複数になる場合は,
駐輪代が一番安い駐輪場にマーク(*)をつけてください.
次のオプション解析を行ってください.
$ java ParkingAnalyzer4 --help
Usage: java ParkingAnalyzer4 [OPTIONS] <駐輪場ファイル...>
-h, --help: このメッセージを表示する.
-s, --station <駅>: 最寄駅を指定する.
-L, --latlon <緯度:経度>: 緯度経度を指定する.
-hもしくは,--helpが指定された場合は,上記のメッセージを表示してください.-sもしくは,--stationが指定された場合は,続いて最寄り駅が指定されていますので,その最寄り駅を元にステップ3と同じ結果を出力してください.- オプションが何も指定されなかった場合は,ステップ2と同じ結果を出力したあと,
駐輪場のリストを出力してください.
ただし,自転車を止めるのに,一番安い駐輪場にマーク(
*)をつけてください. -Lもしくは,--latlonが指定された場合は,このステップでは何も処理する必要はありません. オプションの引数のみを対処してください(無視する,変数に代入するなど).
4-B. 実行例
$ java ParkingAnalyzer4 --help
Usage: java ParkingAnalyzer4 [OPTIONS] <駐輪場ファイル...>
-h, --help: このメッセージを表示する.
-s, --station <駅>: 最寄駅を指定する.
-L, --latlon <緯度:経度>: 緯度経度を指定する.
$ java ParkingAnalyzer4 -h # --help が指定された場合と同じ結果となる.
Usage: java ParkingAnalyzer4 [OPTIONS] <駐輪場ファイル...>
-h, --help: このメッセージを表示する.
-s, --station <駅>: 最寄駅を指定する.
-L, --latlon <緯度:経度>: 緯度経度を指定する.
$ java ParkingAnalyzer4
収容可能台数(自転車) max: N/A, min: N/A, total: N/A, average: N/A
駐輪代(自転車)/日 max: N/A, min: N/A, total: N/A, average: N/A
$ java ParkingAnalyzer4 parking1.csv
収容可能台数(自転車) max: 1600 min: 5, total: 6060, average: 303.000000
駐輪代(自転車)/日 max: 400 min: 100, total: 2650, average: 176.666667
エコステーション21七条西,七条,113,150,34.988854:135.767816
エコステーション21下京区総合庁舎駐輪場,京都,112,400,34.987544:135.755342
京阪六地蔵駅自転車駐車場,六地蔵,600,150,34.931797:135.793047
* ブーブーパーク西院駐輪場,西院,70,100,35.004734:135.731760
蹴上駅前駐輪場,蹴上,15,,35.007734:135.790452
エコステーション21藤森東,藤森,102,150,34.956703:135.770621
ブーブーパーク西院第4駐輪場,西院,50,150,35.004297:135.731679
桂駐輪センター,桂,147,,34.978880:135.703478
京阪納所駐輪場,淀,927,,34.906187:135.720750
* フルーツ自転車パーク京阪清水五条駅,清水五条,91,100,34.996287:135.768888
フルーツ自転車パーク祇園,祇園四条,65,150,35.004621:135.777645
京都市山科駅自転車等駐車場,山科,1600,150,34.991586:135.816507
御池通まちかど駐輪場(烏丸エリア),烏丸御池,210,,35.010979:135.758337
京都市東野駅自転車駐車場,東野,900,150,34.981369:135.816383
二条駅まちかど西駐輪場,二条,203,400,35.010810:135.740801
* フルーツ自転車パーク地下鉄五条駅,五条,60,100,34.998251:135.758071
先斗町自転車駐車場,河原町,328,150,35.005316:135.770444
京都市銀閣寺観光駐車場,京都市営バス銀閣寺道,5,200,35.026596:135.794813
京都駅八条口西自転車駐車場,京都,408,,34.984409:135.755930
エコステーション21 京都鉄道博物館Aエリア,京都,54,150,34.987491:135.743379
$ java ParkingAnalyzer4 -s 北大路 parking1.csv parking2.csv parking3.csv
収容可能台数(自転車) max: 784 min: 125, total: 1069, average: 356.333333
駐輪代(自転車)/日 max: 200 min: 100, total: 300, average: 150.000000
北大路駅東駐車場,北大路,,,135.7588742,35.04504145
キタオオジタウン駐輪場,北大路,784,,135.7588037,35.04438447
今宮駐輪場,北大路,160,200,135.7580788,35.0465088
* ブーブーパーク北大路駅北駐輪場,北大路,125,100,135.7580786,35.0465132
$ java ParkingAnalyzer4 -L 135.758079:35.046513 parking1.csv
収容可能台数(自転車) max: 1600 min: 5, total: 6060, average: 303.000000
駐輪代(自転車)/日 max: 400 min: 100, total: 2650, average: 176.666667
エコステーション21七条西,七条,113,150,34.988854:135.767816
# 途中省略(2つ上の出力と同じ)
エコステーション21 京都鉄道博物館Aエリア,京都,54,150,34.987491:135.743379
# => -L は何も処理しないので,オプションが指定されない場合と同じ結果となる
4-C. ヒント
4-C-a. Arguments型
- 指定されたオプションを格納する
Arguments型を作成しましょう.- 次のフィールドを宣言しましょう.
Boolean型のhelpString型のstationString型のlatlonArrayList<String>型のarguments
void parse(String[] args)メソッドを用意しましょう.- このメソッド内でフィールドに値を代入しましょう.
-sや-Lが指定された場合は,インデックスを1つ進めて,変数に代入しましょう.
- 次のフィールドを宣言しましょう.
4-C-b. parseメソッド内の処理の例
void parse(String[] args){
for(Integer i = 0; i < args.length; i++){
// 途中省略
else if(/* -s もしくは --station が指定された場合の条件式 */){
i++;
this.station = args[i];
}
// 途中省略
}
}
4-D. 評価項目
- 概要に示した評価項目
- オプションの解析を適切に行えているか.
- オプションが指定された場合,指定されなかった場合の両方で,適切に処理できているか.
ステップ5
5-A. 問題説明
このステップでは,ステップ4で指定できるようになったオプションのうち,-L(--latlon)の処理を実装してください.-L後ろには,緯度と経度がコロン(:)で区切られて指定されます.
指定された緯度経度に一番近い駐輪場を出力してください.
2つの緯度経度間の距離の求め方は後述します.
なお,-Lもしくは--latlonオプションが指定された場合,分析は,全てのデータに対して行うようにしてください.
5-B. 実行例
$ java ParkingAnalyzer5 -L 35.06933197593744:135.75574524700642 parking1.csv # 京産大 14号館を指定している
収容可能台数(自転車) max: 1600 min: 5, total: 6060, average: 303.000000
駐輪代(自転車)/日 max: 400 min: 100, total: 2650, average: 176.666667
京都市銀閣寺観光駐車場,京都市営バス銀閣寺道,5,200,35.026596:135.794813
$ java ParkingAnalyzer5 -L 35.06933197593744:135.75574524700642 parking1.csv parking2.csv parking3.csv
収容可能台数(自転車) max: 1721 min: 1, total: 27183, average: 222.811475
駐輪代(自転車)/日 max: 600 min: 0, total: 17850, average: 200.561798
京都市西賀茂自転車駐車場,京都市営バス西賀茂車庫前,250,150,35.064061:135.744613
$ java ParkingAnalyzer5 parking1.csv -s 北大路 -h # --help が指定された場合と同じ結果となる.
Usage: java ParkingAnalyzer5 [OPTIONS] <駐輪場ファイル...>
-h, --help: このメッセージを表示する.
-s, --station <駅>: 最寄駅を指定する.
-L, --latlon <緯度:経度>: 緯度経度を指定する.
$ java ParkingAnalyzer5 # -L 以外を指定した場合はステップ4までと同じ結果となる.
収容可能台数(自転車) max: N/A, min: N/A, total: N/A, average: N/A
駐輪代(自転車)/日 max: N/A, min: N/A, total: N/A, average: N/A
$ java ParkingAnalyzer5 parking1.csv
収容可能台数(自転車) max: 1600 min: 5, total: 6060, average: 303.000000
駐輪代(自転車)/日 max: 400 min: 100, total: 2650, average: 176.666667
エコステーション21七条西,七条,113,150,34.988854:135.767816
# 途中省略(ステップ4の出力と同じ)
エコステーション21 京都鉄道博物館Aエリア,京都,54,150,34.987491:135.743379
$ java ParkingAnalyzer5 -s 北山 parking?.csv
収容可能台数(自転車) max: 600 min: 100, total: 700, average: 350.000000
駐輪代(自転車)/日 max: 150 min: 150, total: 150, average: 150.000000
* 京都市高速鉄道北山駅自転車駐車場,北山,600,150,35.051131:135.765208
GATO.D.M北山第一駐輪場,北山,100,,35.051241:135.767505
5-C. ヒント
5-C-a. 2つの緯度経度間の距離を求める.
大きくヒュベニの公式,球面三角法,測地線航海算法の3つの方法がありますが, ここでは,計算式が簡単な球面三角法について述べます. より詳細な説明は,それぞれの計算方法を検索して調べてみてください.
-
緯度(latitude),経度(longitude)は角度で表されています.
-
$p_1 = (x_1, y_1), p_2 = (x_2, y_2)$ を2点の緯度,経度(ラジアン)とします.
-
赤道半径を $r=6378137.0$(メートル)とします.
- 結果をキロメートルで得たい場合は,$r_{km} = 6378.137$で計算してください.
-
次の式で距離 $d$ がメートル単位で求められます.
- $d = r\arccos({\sin{x_1}\times\sin{x_2} + \cos{x_1} \times \cos{x_2} \times \cos{(y_2 - y_1)}})$
-
例えば次の場合を考えます.
- $p_1 = (35.06933197593744, 135.75574524700642)$ (14号館の入り口)
- $p_2 = (35.0698994522298, 135.75674034655094)$ (第2実験室棟の入り口)
- これらの緯度,経度は度数法で表現されているため,ラジアンに変換します.
- その後,計算式に当てはめると,110.50094719125913 という結果が得られます.
- 2つの緯度経度間の直線距離が約 110 m であることを表しています.
5-C-b. 数学関数
- $\arccos x = \cos^{-1} x$ であり,Java では,
Math.acosで求められます. - $\sin$,$\cos$ は それぞれ
Math.sin,Math.cosで求められます.
緯度経度を表す型
- 緯度,経度を表す型
LatLonを作成しましょう.- 2つの
Double型のフィールドlatitude(緯度),longitude(経度)を用意しましょう. void parse(String latlon)メソッドで,緯度:経度で与えられた緯度経度を解析して,フィールドに代入しましょう.Double distance(LatLon latlon)メソッドで,thisと引数のlatlon間の距離を計算しましょう.
- 2つの
なお,parking1.csvなどの各行の最後は,経度,緯度の順序であることに注意してください.
5-D. 評価項目
- 概要に示した評価項目
- 2点の緯度経度から距離を求められるか.
ステップ6
6-A. 問題説明
結果を保存するオプション -o,--output を追加してください.
また,出力するリストにある駐車場間の距離を求めて出力する -d,distanceオプションも追加してください.
Usage: java ParkingAnalyzer6 [OPTIONS] <駐輪場ファイル...>
-h, --help: このメッセージを表示する.
-s, --station <駅>: 最寄駅を指定する.
-L, --latlon <緯度:経度>: 緯度経度を指定する.
-o, --output <FILE>: 結果を保存する.
-d, --distance <FILE>: 駐車場リスト間の距離をcsvで出力する.
結果を保存するオプションが指定された場合,標準出力には何も表示させず,指定されたファイルに書き出すようにしてください.
なお,上記のヘルプメッセージは,outputオプションを無視してください.
6-B. 実行例
$ java ParkingAnalyzer6
収容可能台数(自転車) max: N/A, min: N/A, total: N/A, average: N/A
駐輪代(自転車)/日 max: N/A, min: N/A, total: N/A, average: N/A
$ java ParkingAnalyzer6 --output dest parking1.csv
$ cat dest
収容可能台数(自転車) max: 1600 min: 5, total: 6060, average: 303.000000
駐輪代(自転車)/日 max: 400 min: 100, total: 2650, average: 176.666667
$ java ParkingAnalyzer6 --output dest --help
Usage: java ParkingAnalyzer6 [OPTIONS] <駐輪場ファイル...>
-h, --help: このメッセージを表示する.
-s, --station <駅>: 最寄駅を指定する.
-L, --latlon <緯度:経度>: 緯度経度を指定する.
-o, --output <FILE>: 結果を保存する.
-d, --distance <FILE>: 駐車場間の距離をcsvで出力する.
$ java ParkingAnalyzer6 --output dest.csv parking1.csv --distance distance.csv
$ cat distance.csv # 実際には1駐輪場が1行で出力される.
エコステーション21七条西,0.000000,
エコステーション21下京区総合庁舎駐輪場,1.146916,0.000000,
京阪六地蔵駅自転車駐車場,6.755791,7.095364,0.000000,
ブーブーパーク西院駐輪場,3.733021,2.878563,9.857946,0.000000,
蹴上駅前駐輪場,2.945857,3.911787,8.456576,5.361950,0.000000,
... 途中省略
エコステーション21 京都鉄道博物館Aエリア,2.233822,1.091078,7.679255,2.192455,4.848133,4.233392,2.153666,3.763319,9.283261,2.523967,3.660690,6.684780,2.949053,6.693068,2.606437,1.797200,3.166809,6.398749,1.195024,0.000000,
$ cat dest.csv
収容可能台数(自転車) max: 1600 min: 5, total: 6060, average: 303.000000
駐輪代(自転車)/日 max: 400 min: 100, total: 2650, average: 176.666667
エコステーション21七条西,七条,113,150,34.988854:135.767816
# 途中省略(ステップ4の出力と同じ)
エコステーション21 京都鉄道博物館Aエリア,京都,54,150,34.987491:135.743379
$ java ParkingAnalyzer6 parking1.csv --distance distance.csv parking3.csv --station 北大路
収容可能台数(自転車) max: 784 min: 125, total: 1069, average: 356.333333
駐輪代(自転車)/日 max: 200 min: 100, total: 300, average: 150.000000
キタオオジタウン駐輪場,北大路,784,,35.044384:135.758804
今宮駐輪場,北大路,160,200,35.046509:135.758079
* ブーブーパーク北大路駅北駐輪場,北大路,125,100,35.046513:135.758079
$ cat distance.csv # 北大路に近い駐車場間の距離
キタオオジタウン駐輪場,0.000000,
今宮駐輪場,0.245534,0.000000,
ブーブーパーク北大路駅北駐輪場,0.246011,0.000485,0.000000,
6-C. ヒント
出力先を切り替える.
Arguments型に対して,以下の処理を行いましょう.- 出力先を格納する
String型のoutputを宣言してください. - 出力先を表す
PrintWriter型のoutを宣言してください.初期値は代入しなくて良いです. - オプション解析(
Arguments型のparseメソッド)にて,出力先をArgumentsのoutputに代入するようにしてください. PrintWriterを返すwriterメソッドを用意しましょう.outに何も代入されていなければ(nullであれば),次の処理を行ってください.outputに文字が代入されていれば,そのファイルを開きPrintWriter型でラップして,outに代入しましょう.out = new PrintWriter(new FileWriter(output));
outputに文字が代入されていなければ,System.outをPrintWriter型でラップしてoutに代入しましょう.out = new PrintWriter(System.out);
- 最後に,
outをreturnしてください.
closeメソッドを用意しましょう.引数,返り値はありません.outに値が代入されていれば(nullでなければ),closeしてください.
- 出力先を格納する
- 今まで,
System.out.printfを呼び出していたところを,Argumentsの実体に対して,writerメソッドを呼び出してください.System.out.println->arguments.writer().println- 全ての処理が終われば,
arguments.close()を呼び出して出力ストリームを閉じてください.
駐車場間の距離のCSVの使い方
各駐輪場間の距離を求められれば,多次元尺度構成法(MDS; Multi-Dimendional Scaling)によって,地図にマッピングが可能になります. こちらのページでマッピングを確認できます.
parking1.csvに記載されている駐輪場間の距離を求めたものは, distance.csvからダウンロードできます. このファイルの内容をMDSでマッピングすると, このような画像になります.
一番下の駐輪場が桂駐輪センター,その上の2つ重なっている点が,ブーブーパーク西院駐輪場とブーブーパーク西院第4駐輪場です. つまり,左が北を指すようにプロットされていることがわかります.
リンク
- 多次元尺度構成法 (Wikipedia)
- MDSのアルゴリズム (www.benfrederickson.com)