以下のステップを実行する前に,概要の 課題のデータから必要なデータをダウンロードしておいてください.
reading.csv,writing.csvは同じフォーマットのデータで,左カラムから順に以下のデータが書かれています.
コマンドライン引数で問題番号とデータファイルが指定されます. 指定されたデータファイルを読み,指定された問題番号のスコアの頻度(%)を出力してください. コマンドライン引数で与えられるデータは必ず1つであり,正しいフォーマットのファイルが渡されると仮定して構いません.
$ 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)次の流れで処理すれば良いでしょう.
HashMap<String, Integer>型の変数mapを宣言し,初期化する.,)で区切る.mapからgetする.nullが返されれば),0で初期化する.mapに登録し直す.mapのバリューの全てを足し合わせることで,受験者数(examineeCount)が数えられます.HashMap<String, Integer> 型の変数の要素(キーとバリューのペア)を順に繰り返す.
100.0 / examineeCountを掛けるとそのスコアの割合になります.
Integer型のままだと小数点以下が計算されない点に注意してください.問題番号ごとに,スコアの割合を算出してcsv形式で出力してください. 各桁(column)にスコアを並べ,各行(row)には問題番号を並べてください.
$ 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表形式の値を管理するために,多次元配列を用いるのではなく,
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のスコアを取得した人数を取得する.学生ごとにスコアをまとめて csv として出力してください. また,学生ごと,問題ごとにスコアの最大,最小,平均も出力してください.
次のような出力になっています.
学生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.666667Statsを用意しましょう.
max,最小値を表すmin,値の合計値を表すsum,値の数を表すcountを用意しましょう.max,最小値を返すmin,平均値を返すaverageを定義しましょう.putメソッドを用意してください.
putメソッドでは,追加された値が最小値,最大値に相当する場合は,それぞれに代入してください.sumに値を追加し,countをインクリメントしてください.これらの2つの変数はaverageメソッドで利用します.StudentScore を用意しましょう.
StudentScoreはidと課題番号とその評価をキー,バリューとするHashMap型の変数をフィールドとして持つと良いでしょう.Stats型の変数をフィールドに定義しましょう.max,最小値を返すmin,平均値を返すaverageメソッドを用意しましょう.
Stats型の変数から値を取得します.put(String, Integer)を用意し,putで追加された値をStats型の変数にputしましょう.ステップ3の結果に加えて,ステップ3の結果をヒートマップとして出力してください. ヒートマップとは,二次元データの値の大小を色や濃度で表したグラフの一つです. 点数の範囲を調べ,最小値が0,最大値が255となるように点数をスケールさせてください. その計算結果を RGB のどれかに当てはめると良いでしょう(画素の計算方法).
ヒートマップの作成には,EZ.javaもしくは,pnmフォーマットで作成してください.
ファイル名はEZ.java を使った場合は heatmap.png, pnmフォーマットの場合は,heatmap.ppmとしてください.
EZ.javaを使って画像ファイルを生成する方法はヒント EZ.javaで画像ファイルを作成するに示します.
EZ.javaを用いた場合の実行例を示します.
画像をクリックするごとに赤,緑,青,黄,マゼンタ,シアン,グレー,HSVでヒートマップを作成したものに切り替わります.
色が黒に近いほど点数が低く,白(実際は透明色)は未受験,色が濃くなるほど良い点数であることを表しています.
このように画面に表示されるだけでなく,heatmap.pngも出力されている点に注意してください.
なお,色の種類は自由に決めてもらって構いません(赤,緑,青,黄,マゼンタ,シアン,グレー,HSVのいずれか1つ).
横軸が学生,縦軸が課題を表しています.右に行くほどIDが増加し,下に向けて課題番号が増加していきます. また,1つの課題の点数に3×3のピクセルを使っています. このように拡大しない場合は,以下の画像の1/3程度の大きさになります.
reading.csv
writing.csv
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.Colorをimportする必要があります.
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)); // 赤の場合
}各学生が各課題の提出までに要した時間を計算してください. 入力データの6,7カラム目が開始時刻,提出時刻です. この2つから所要時間を計算し,ステップ4の出力に,その時間を追加してください. 各行を次のように出力してください.
学生ID,課題1の点数,課題1の所要時間,課題2の点数,課題2の所要時間,...,点数の最大値,点数の最小値,点数の平均値上記に加えて,次の内容をオプションで指定できるようにしてください. ただし,提出時間のヒートマップの作成はステップ6で対応するため, ここではオプションで受け取るだけで構いません(ヒートマップの出力部分はステップ4と同じで構いません).
".ppm" にしてください.score),提出時間のヒートマップ(time)
id),成績の平均点順(score),所要時間の平均順(time)のいずれかでソートする(昇順).なお,ステップ3で作成した独自型(StudentScore)を修正すると,ステップ3が動作しなくなるためStudentScore5に変更しておきましょう.
$ 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オプションの解析では,オプションの指定方法が間違っているケースは考える必要はありません.
オプションの内容を記録するために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.java,StudentScoreComparator.java,
StudentTakenTimeComparator.javaに保存し,同じディレクトリに置いてください.
StudentIdComparator.javaimport java.util.Comparator;
public class StudentIdComparator implements Comparator<StudentScore5> {
public int compare(StudentScore5 ss1, StudentScore5 ss2){
return ss1.id.compareTo(ss2.id);
}
}StudentScoreComparator.javaimport java.util.Comparator;
public class StudentScoreComparator implements Comparator<StudentScore5> {
public int compare(StudentScore5 ss1, StudentScore5 ss2){
return Double.compare(ss1.average(), ss2.average());
}
}StudentTakenTimeComparator.javaaverageOfTakenTimeメソッドを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で指定したヒートマップを出力するようプログラムを変更してください.
つまり,--heatmap scoreが指定された場合(もしくは--heatmapオプションが指定されなかった場合)は点数のヒートマップ,
--heatmap timeが指定された場合は提出までに要した時間のヒートマップを出力してください.
加えて,ヒートマップもステップ5で指定したソート項目でソートされるようにしてください.
reading.csvのスコアのヒートマップ
reading.csvの時間のヒートマップ