最終課題

ステップ0

以下のステップを実行する前に,概要課題のデータから必要なデータをダウンロードしておいてください.

reading.csvwriting.csvは同じフォーマットのデータで,左カラムから順に以下のデータが書かれています.

ステップ1

1-A. 問題説明

コマンドライン引数で問題番号とデータファイルが指定されます. 指定されたデータファイルを読み,指定された問題番号のスコアの頻度(%)を出力してください. コマンドライン引数で与えられるデータは必ず1つであり,正しいフォーマットのファイルが渡されると仮定して構いません.

1-B. 実行例

$ java ScoreAnalyzer1 1 reading.csv # 問題番号1のスコアの頻度を出力する.
  :  2.308 ( 3/130) # 問題番号1を時間内に提出できなかった学生が3名(2.308%)いる.
 6:  7.692 (10/130) # 問題番号1で6点をとった人が130名中10名(7.692%)いる.
 8: 20.000 (26/130)
10: 70.000 (91/130)
$ java ScoreAnalyzer1 2 reading.csv
 0:  4.615 ( 6/130)
  :  0.769 ( 1/130)
 2: 10.000 (13/130)
 4: 24.615 (32/130)
 6: 31.538 (41/130)
 8:  6.923 ( 9/130)
10: 21.538 (28/130)
$ java ScoreAnalyzer1 4 reading.csv
  :  0.800 ( 1/125)
 0:  2.400 ( 3/125)
 2:  4.800 ( 6/125)
 4:  5.600 ( 7/125)
 6: 24.800 (31/125)
 8: 42.400 (53/125)
10: 19.200 (24/125)
$ java ScoreAnalyzer1 5 writing.csv
 1: 77.419 (72/93)
 3: 11.828 (11/93)
 5: 10.753 (10/93)

1-C. ヒント

処理の流れ

次の流れで処理すれば良いでしょう.

  1. HashMap<String, Integer>型の変数mapを宣言し,初期化する.
  2. コマンドライン引数で渡された文字列をファイルとして順に処理する.
    1. 1行をコンマ(,)で区切る.
    2. 3番目の要素(インデックスが2)が問題番号であるため,指定された問題番号であるかを確認する.
    3. 指定された問題番号の場合,当該スコアの人数を調べる.つまり,スコア(5番目の要素; インデックスが4)をキーとして人数(バリュー)をmapからgetする.
    4. 当該スコアの人数が0であれば(nullが返されれば),0で初期化する.
    5. 人数を +1 し,再度 mapに登録し直す.
  3. 受験者数を調べる.
    1. mapのバリューの全てを足し合わせることで,受験者数(examineeCount)が数えられます.
  4. HashMap<String, Integer> 型の変数の要素(キーとバリューのペア)を順に繰り返す.
    1. キーがスコア,バリューがそのスコアの人数を表します.
    2. バリューの数値に100.0 / examineeCountを掛けるとそのスコアの割合になります.
      • Integer型のままだと小数点以下が計算されない点に注意してください.

1-D. 評価項目

ステップ2

2-A. 問題説明

問題番号ごとに,スコアの割合を算出してcsv形式で出力してください. 各桁(column)にスコアを並べ,各行(row)には問題番号を並べてください.

2-B. 実行例

$ java ScoreAnalyzer2 writing.csv
,1,3,5
1,,,100.000
2,70.536,14.286,15.179
3,25.893,25.893,48.214
4,41.818,13.636,44.545
5,77.419,11.828,10.753

2-C. ヒント

表形式の値を管理するために,多次元配列を用いるのではなく, HashMap<String, HashMap<String, Integer>> を用いるようにしてください. 次のような構造にすると良いでしょう.

HashMap<String, HashMap<String, Integer>> map = new HashMap<>();
// ... map に値をputしていく.
HashMap<String, Integer> scores = map.get("1");   // 問題番号1のスコア一覧を取得する.
Integer numberOfAInAssignment1 = scores.get("A"); // 問題番号1でAのスコアを取得した人数を取得する.

2-D. 評価項目

ステップ3

3-A. 問題説明

学生ごとにスコアをまとめて csv として出力してください. また,学生ごと,問題ごとにスコアの最大,最小,平均も出力してください.

3-B. 実行例

次のような出力になっています.

学生ID,課題1の点数,課題2の点数,...., 最大値,最小値,平均点
....
,課題1の最大値,課題2の最大値,...
,課題1の最小値,課題2の最小値,...
,課題1の平均点,課題2の平均点,...
$ java ScoreAnalyzer3 reading.csv # プログラム読解のスコア
88,10,2,4,6,8,10,2,6.000000
89,10,,,,,10,10,10.000000
110,10,4,10,8,10,10,4,8.400000
111,10,4,8,10,10,10,4,8.400000
112,10,8,8,10,10,10,8,9.200000
# 途中省略
83,10,8,0,8,10,10,0,7.200000
84,,6,,,,6,6,6.000000
85,10,6,10,8,0,10,0,6.800000
86,,,,0,,0,0,0.000000
87,10,10,8,8,8,10,8,8.800000
,10,10,10,10,10
,6,0,0,0,0
,9.275591,5.829457,6.047244,7.177419,8.474576
$ java ScoreAnalyzer3 writing.csv # プログラム作成のスコア
88,5,1,1,1,1,5,1,1.800000
89,5,1,1,,,5,1,2.333333
110,5,5,5,5,,5,5,5.000000
111,5,1,5,5,1,5,1,3.400000
112,5,1,5,5,3,5,1,3.800000
# 途中省略
83,5,1,1,1,1,5,1,1.800000
84,5,,,,,5,5,5.000000
85,5,3,1,1,1,5,1,2.200000
86,,,3,5,,5,3,4.000000
87,5,3,5,3,3,5,3,3.800000
,5,5,5,5,5
,5,1,1,1,1
,5.000000,1.892857,3.446429,3.054545,1.666667

3-C. ヒント

条件を満たすために

3-D. 評価項目

ステップ4

4-A. 問題説明

ステップ3の結果に加えて,ステップ3の結果をヒートマップとして出力してください. ヒートマップとは,二次元データの値の大小を色や濃度で表したグラフの一つです. 点数の範囲を調べ,最小値が0,最大値が255となるように点数をスケールさせてください. その計算結果を RGB のどれかに当てはめると良いでしょう(画素の計算方法).

ヒートマップの作成には,EZ.javaもしくは,pnmフォーマットで作成してください. ファイル名はEZ.java を使った場合は heatmap.png, pnmフォーマットの場合は,heatmap.ppmとしてください. EZ.javaを使って画像ファイルを生成する方法はヒント EZ.javaで画像ファイルを作成するに示します.

4-B. 実行例

EZ.javaを用いた場合の実行例を示します.

画像をクリックするごとに赤,緑,青,黄,マゼンタ,シアン,グレー,HSVでヒートマップを作成したものに切り替わります. 色が黒に近いほど点数が低く,白(実際は透明色)は未受験,色が濃くなるほど良い点数であることを表しています. このように画面に表示されるだけでなく,heatmap.pngも出力されている点に注意してください. なお,色の種類は自由に決めてもらって構いません(赤,緑,青,黄,マゼンタ,シアン,グレー,HSVのいずれか1つ).

横軸が学生,縦軸が課題を表しています.右に行くほどIDが増加し,下に向けて課題番号が増加していきます. また,1つの課題の点数に3×3のピクセルを使っています. このように拡大しない場合は,以下の画像の1/3程度の大きさになります.

reading.csv

拡大しない場合の大きさ(赤画素)

writing.csv

拡大しない場合の大きさ(緑画素)

4-C. ヒント

EZ.javaで画像ファイルを作成する

EZ.javaで画像ファイルを出力するには,次の3つのステップをへる必要があります.

具体的には,EZ.javaを用いて描画した後,以下のoutputImageを呼び出すと PNG 形式の画像が heatmap.png に出力されます. outputImageメソッドの引数の width, height は画像の大きさです.

void outputImage(Integer width, Integer height) throws IOException {
    BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
    Graphics g = image.createGraphics();
    EZ.app.paintComponent(g); // image に図形を描画する.
    // image を "png" フォーマットで "heatmap.png" に出力する.
    ImageIO.write(image, "png", new File("heatmap.png"));
}

このメソッドを用いる場合は,以下の import文を追加しておいてください.

import java.awt.image.BufferedImage;
import java.awt.Graphics;
import javax.imageio.ImageIO;

画素の計算方法

Integer max = 10;
Color calculatePixelColor(Integer score){
    if(score == null){
        return new Color(0xff, 0xff, 0xff, 0xff); // 白の透明色
    }
    Double color = Double.valueOf(255.0 * score / maxScore);
    return new Color(value.intValue(), 0, 0); // 赤の場合
}

Color型を利用するには,java.awt.Colorimportする必要があります.

HSV

HSVとは色相(Hue),彩度(Saturation),明度(Value)の3つで1つの色を表す色モデルです. HSB(Hue, Saturation, Brightness)と表現されることもあります. 以下の画像は色相を30度ずつ変更した場合の色の変化です. 色相が0と360は同じ色です. クリックで確認してください.

ヒートマップを作るときに,スコアの点数を色相の0(赤)〜240(青)に割り当てることで,より自然なヒートマップになります. 以下の処理を参考にすると良いでしょう(HSBtoRGBは0.0〜1.0の範囲の数値を受け取ります).

Color calculatePixelColor(Integer score){
    if(score == null){
        return new Color(0xff, 0xff, 0xff, 0xff); // 白の透明色
    }
    Float hue = Float.valueOf((1.0f - 1.0f * score / maxScore) * (240.0f / 360.0f));
    return new Color(Color.HSBtoRGB(hue, 1.0f, 1.0f)); // 赤の場合
}

4-D. 評価項目

ステップ5

5-A. 問題説明

各学生が各課題の提出までに要した時間を計算してください. 入力データの6,7カラム目が開始時刻,提出時刻です. この2つから所要時間を計算し,ステップ4の出力に,その時間を追加してください. 各行を次のように出力してください.

学生ID,課題1の点数,課題1の所要時間,課題2の点数,課題2の所要時間,...,点数の最大値,点数の最小値,点数の平均値

上記に加えて,次の内容をオプションで指定できるようにしてください. ただし,提出時間のヒートマップの作成はステップ6で対応するため, ここではオプションで受け取るだけで構いません(ヒートマップの出力部分はステップ4と同じで構いません).

なお,ステップ3で作成した独自型(StudentScore)を修正すると,ステップ3が動作しなくなるためStudentScore5に変更しておきましょう.

5-B. 実行例

$ java ScoreAnalyzer5 --help # <= ヘルプメッセージを表示する.
java ScoreAnalyzer5 [OPTIONS] <FILENAME.CSV>
OPTIONS
    --help           このメッセージを表示して終了する.
    --dest <DEST>    ヒートマップの出力先を指定する.
    --sort <ITEM>    指定された項目の昇順でソートする.
    --heatmap <TYPE> ヒートマップの種類を指定する.scoreもしくはtime.
$ java ScoreAnalyzer5 --sort id --dest heatmap_score.png reading.csv
# heatmap_score.png.ppm にヒートマップを出力する.
1,,,,,,,,,10,3,10,10,10.000000
2,10,8,0,9,8,8,6,5,8,5,10,0,6.400000
3,10,5,6,5,4,12,8,5,,,10,4,7.000000
4,6,2,10,7,8,9,8,5,10,8,10,6,8.400000
5,10,4,2,13,10,6,10,8,10,7,10,2,8.400000
# 途中省略
140,6,4,0,1,2,6,6,9,8,7,8,0,4.400000
141,10,2,10,5,6,3,8,3,10,3,10,6,8.800000
142,10,3,10,5,,,8,3,10,1,10,8,9.500000
143,10,6,10,8,8,9,10,11,10,10,10,8,9.600000
144,10,7,6,9,6,8,2,9,10,8,10,2,6.800000
,10,10,10,10,10
,6,0,0,0,0
,9.275591,5.829457,6.047244,7.177419,8.474576
$ java ScoreAnalyzer5 writing.csv --sort score
86,,,,,,,0,0,,,0,0,0.000000
139,8,6,4,11,0,4,2,1,,,8,0,3.500000
100,,,0,10,4,7,6,7,5,12,6,0,3.750000
113,,,2,4,2,6,8,3,,,8,2,4.000000
116,10,5,,,2,0,,,0,10,10,0,4.000000
16,10,15,4,8,0,10,6,3,0,12,10,0,4.000000
# 途中省略
143,10,6,10,8,8,9,10,11,10,10,10,8,9.600000
50,10,4,10,3,10,3,8,8,10,2,10,8,9.600000
58,10,3,10,4,10,6,8,7,10,9,10,8,9.600000
103,10,4,10,5,8,4,10,6,10,2,10,8,9.600000
89,10,6,,,,,,,,,10,10,10.000000
1,,,,,,,,,10,3,10,10,10.000000
136,10,7,10,10,,,,,,,10,10,10.000000
51,10,5,10,7,,,10,9,,,10,10,10.000000
,10,10,10,10,10
,6,0,0,0,0
,9.275591,5.829457,6.047244,7.177419,8.474576

5-C. ヒント

オプションの解析

オプションの解析では,オプションの指定方法が間違っているケースは考える必要はありません. オプションの内容を記録するためにArguments型を用意しましょう. Arguments型にはフィールドとして,オプションの内容を保持する変数を宣言してください. そして,Arguments型に次のようなparseを用意してください.

void parse(String[] args){
    for(Integer i = 0; i < args.length; i++){
        if(!args[i].startsWith("--")){
            arguments.add(args[i]);
        }
        else {
            i = parseOption(args, i);
        }
    }
}
Integer parseOption(String[] args, Integer i) {
    if(Objects.equals(args[i], "--dest")){
        i++;
        this.dest = args[i];
    }
    ...
    return i;
}

拡張子と出力フォーマットを合わせる.

拡張子が望むものかどうかを確認し,望むものでなければ拡張子を追加しましょう.

String updateExtension(String fileName, String wontExtention){
    if(fileName.endsWith(wontExtention)){
        return fileName;
    }
    return fileName + wontExtention;
    // 拡張子を置き換えたい場合は次の処理.
    // Integer index = fileName.lastIndexOf(".");
    // if(index < 0)
    //     return fileName + wontExtention;
    // return fileName.substring(0, index) + wontExtention;
}

上記のメソッドを定義しておくと,次のような結果が得られます.

String name1 = updateExtension("heatmap.png", ".png"); // => "heatmap.png"
String name2 = updateExtension("heatmap.png", ".ppm"); // => "heatmap.png.ppm"
String name3 = updateExtension("heatmap", ".ppm");     // => "heatmap.ppm"

指定の方法でソートする.

次の3つの比較器を用意し,それぞれStudentIdComparator.javaStudentScoreComparator.javaStudentTakenTimeComparator.javaに保存し,同じディレクトリに置いてください.

StudentIdComparator.java
import java.util.Comparator;
public class StudentIdComparator implements Comparator<StudentScore5> {
    public int compare(StudentScore5 ss1, StudentScore5 ss2){
        return ss1.id.compareTo(ss2.id);
    }
}
StudentScoreComparator.java
import java.util.Comparator;
public class StudentScoreComparator implements Comparator<StudentScore5> {
    public int compare(StudentScore5 ss1, StudentScore5 ss2){
        return Double.compare(ss1.average(), ss2.average());
    }
}
StudentTakenTimeComparator.java

averageOfTakenTimeメソッドをStudentScore5に定義し,所要時間の平均を返すようにしてください.

import java.util.Comparator;
public class StudentTakenTimeComparator implements Comparator<StudentScore5> {
    public int compare(StudentScore5 ss1, StudentScore5 ss2){
        return Double.compare(ss1.averageOfTakenTime(), ss2.averageOfTakenTime());
    }
}
ソートされた学生一覧を取得する.

学生一覧がHashMap<String, StudentScore5>に保存されている場合,次のようなプログラムを書くことで, StudentScore5の一覧がソートされた状態で取得できます.

ArrayList<StudentScore5> sortedStudentList(HashMap<String, StudentScore5> map, String sortKey){
    Arraylist<StudentScore5> list = new ArrayList<>(map.values());
    if(Objects.equals(sortKey, "id")){
        Collections.sort(list, new StudentIdComparator());
    }
    else if(Objects.equals(sortKey, "score")){
        Collections.sort(list, new StudentScoreComparator());
    }
    else if(Objects.equals(sortKey, "time")){
        Collections.sort(list, new StudentTakenTimeComparator());
    }
    return list;
}

5-D. 評価項目

ステップ6

6-A. 問題説明

ステップ5で指定したヒートマップを出力するようプログラムを変更してください. つまり,--heatmap scoreが指定された場合(もしくは--heatmapオプションが指定されなかった場合)は点数のヒートマップ, --heatmap timeが指定された場合は提出までに要した時間のヒートマップを出力してください. 加えて,ヒートマップもステップ5で指定したソート項目でソートされるようにしてください.

6-B. 実行例

6-C. ヒント

6-D. 評価項目