最終課題

ステップ0

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

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

  • 小テストが行われた日付
  • reading/writingの区別
  • 課題番号
  • 学生ID
  • 点数
  • 開始時刻
  • 提出時刻

ステップ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. ヒント

条件を満たすために

  • 平均値,最大値,最小値をまとめる型Statsを用意しましょう.
    • フィールドに最大値を表すmax,最小値を表すmin,値の合計値を表すsum,値の数を表すcountを用意しましょう.
    • メソッドに,最大値を返すmax,最小値を返すmin,平均値を返すaverageを定義しましょう.
    • そして,値を追加するputメソッドを用意してください.
      • putメソッドでは,追加された値が最小値,最大値に相当する場合は,それぞれに代入してください.
      • 加えて,sumに値を追加し,countをインクリメントしてください.これらの2つの変数はaverageメソッドで利用します.
  • 次に,1人の学生の成績をまとめる型 StudentScore を用意しましょう.
    • StudentScoreidと課題番号とその評価をキー,バリューとするHashMap型の変数をフィールドとして持つと良いでしょう.
    • また,上記で定義したStats型の変数をフィールドに定義しましょう.
    • そして,評価の最大値を返すmax,最小値を返すmin,平均値を返すaverageメソッドを用意しましょう.
      • それぞれ,Stats型の変数から値を取得します.
    • 最後に,課題番号とその評価を追加するput(String, Integer)を用意し,putで追加された値をStats型の変数にputしましょう.

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を使い,自由に描画してください.
  • 次に,描画した内容元に画像(Image型の実体)を作成します.
  • 最後に画像をファイルに出力します.

具体的には,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と同じで構いません).

  • ヒートマップの出力先
    • ただし,拡張子と出力フォーマットは一致させてください.
    • ppm フォーマットで出力する時は,拡張子を ".ppm" にしてください.
  • ヒートマップの種類
    • 点数のヒートマップか(score),提出時間のヒートマップ(time
      • 提出時間のヒートマップの作成はステップ6で対応するため,オプションで指定可能なようにするだけで構いません.
  • 学生のソート方法
    • ID順(id),成績の平均点順(score),所要時間の平均順(time)のいずれかでソートする(昇順).
  • ヘルプメッセージの表示

なお,ステップ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. 実行例

  • reading.csvのスコアのヒートマップ

    ソートなし

    IDでソート

    スコアでソート

    時間でソート

  • reading.csvの時間のヒートマップ

    ソートなし

    IDでソート

    スコアでソート

    時間でソート

6-C. ヒント

6-D. 評価項目

  • 概要に示した評価項目
  • データを変更しても,例外なく実行結果を出力できるか
    • 人数やスコアの有効値が変更されても例外なく出力されるか.