以下のステップを実行する前に,概要の 課題のデータから必要なデータをダウンロードしておいてください.
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.666667
Stats
を用意しましょう.
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.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で指定したヒートマップを出力するようプログラムを変更してください.
つまり,--heatmap score
が指定された場合(もしくは--heatmap
オプションが指定されなかった場合)は点数のヒートマップ,
--heatmap time
が指定された場合は提出までに要した時間のヒートマップを出力してください.
加えて,ヒートマップもステップ5で指定したソート項目でソートされるようにしてください.
reading.csv
のスコアのヒートマップ
reading.csv
の時間のヒートマップ