最終課題
ステップ0
以下のステップを実行する前に,概要の 課題のデータから必要なデータをダウンロードしておいてください.
reading.csv
,writing.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. ヒント
処理の流れ
次の流れで処理すれば良いでしょう.
HashMap<String, Integer>
型の変数map
を宣言し,初期化する.- コマンドライン引数で渡された文字列をファイルとして順に処理する.
- 1行をコンマ(
,
)で区切る. - 3番目の要素(インデックスが2)が問題番号であるため,指定された問題番号であるかを確認する.
- 指定された問題番号の場合,当該スコアの人数を調べる.つまり,スコア(5番目の要素; インデックスが4)をキーとして人数(バリュー)を
map
からget
する. - 当該スコアの人数が0であれば(
null
が返されれば),0で初期化する. - 人数を +1 し,再度
map
に登録し直す.
- 1行をコンマ(
- 受験者数を調べる.
map
のバリューの全てを足し合わせることで,受験者数(examineeCount
)が数えられます.
HashMap<String, Integer>
型の変数の要素(キーとバリューのペア)を順に繰り返す.- キーがスコア,バリューがそのスコアの人数を表します.
- バリューの数値に
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
を用意しましょう.StudentScore
はid
と課題番号とその評価をキー,バリューとする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.Color
をimport
する必要があります.
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
)のいずれかでソートする(昇順).
- ID順(
- ヘルプメッセージの表示
なお,ステップ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.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-D. 評価項目
- 概要に示した評価項目
- オプションの解析を適切に行えているか.
- オプションが指定された場合,指定されなかった場合の両方で,適切に処理できているか.
- データを変更しても,例外なく実行結果を出力できるか
- 人数やスコアの有効値が変更されても例外なく出力されるか.
ステップ6
6-A. 問題説明
ステップ5で指定したヒートマップを出力するようプログラムを変更してください.
つまり,--heatmap score
が指定された場合(もしくは--heatmap
オプションが指定されなかった場合)は点数のヒートマップ,
--heatmap time
が指定された場合は提出までに要した時間のヒートマップを出力してください.
加えて,ヒートマップもステップ5で指定したソート項目でソートされるようにしてください.
6-B. 実行例
reading.csv
のスコアのヒートマップreading.csv
の時間のヒートマップ
6-C. ヒント
6-D. 評価項目
- 概要に示した評価項目
- データを変更しても,例外なく実行結果を出力できるか
- 人数やスコアの有効値が変更されても例外なく出力されるか.