第16週目課題

課題1601

複素数を表すクラス Complex をパッケージ j2.lesson03 に作成しなさい。

複素数とは実数と虚数を組み合わせた数のことである。

このクラスに次のようなインスタンスフィールドを用意すること。また、これらは全て private, final として指定すること。

また、次のようなコンストラクタを用意すること。これは public として指定すること。

また、次のようなインスタンスメソッドを用意すること。これらは public として指定すること。

上記に記載されていないインスタンスフィールド、インスタンスメソッド、クラスメソッド、コンストラクタは必要ならば自由に用意して構わない。

なお、z = a + bi, w = c + di であるような複素数について考えた場合、次のような計算を行う。

加算
z + w = (a + c) + (b + d)i
乗算
z * w = (a * c - b * d) + (a * d + b * c)i

また、add, mult の「この操作によって、this, other に変更を加えてはならない」というのは、例えば add ならば加算を行った結果として this.r, this.i, other.r, other.i の値が変わってはいけないということである。

例えば、次のようなメソッドは間違いである。

public Complex add(Complex other) {
    this.r += other.r;
    this.i += other.i;
    ...
}

上記の例では、this が変更されている。このようなことを避けるために、通常は計算結果から新しいインスタンスを作成し、そのインスタンスを return する。

例えば、次のように書けばよい。

public Complex add(Complex other) {
    double rResult = ...
    double iResult = ...
    return new Complex(rResult, iResult);
}

また、toString() メソッドで文字列を作成する必要がある場合、

return a + "," + b;

上のように書くと、"(aの内容)+(bの内容)" という文字列を作ることができる。例えば、a=1.0, b=-2.0 の場合、"1.0,-2.0"という文字列が作成される。

手順

指定した箇所で必ずテストを行うこと。

  1. 各メソッドに対する詳細な擬似コードを作成する
  2. パッケージ j2.lesson03 にクラス Complex を作成
  3. 指定したインスタンスメソッドやコンストラクタを作成する
  4. テスト項目「Complexに対する骨格テスト」をパス
  5. インスタンスメソッドやコンストラクタの中身を書く (演習と同様)
  6. main メソッドを書いて簡単なテストを行う
  7. テスト項目「Complexに対する単体テスト」をパス

テストの失敗メッセージ

骨格テスト

メッセージ 詳細
(クラス名), existence パッケージ内に課題で指定したクラスが存在していない。パッケージやクラス名を確認
(メンバ名), existence 指定されたメンバが存在しない
(メンバ名), public メンバを作る際に public の指定がない
(メンバ名), private メンバを作る際に private の指定がない
(メンバ名), final メンバを作る際に final の指定がない
(メンバ名), not static メンバを作る際に static が余計についている
(メンバ名), type <T> メンバを作る際に型の指定を間違っている。正しくは <T>

単体テスト

メッセージ 詳細
期待された結果と異なります (メソッド名)を起動した結果が期待された結果と異なる。テスト項目を参照
toString() の形式が異なります toString() メソッドを呼び出した結果が指定された形式でない
副作用があります add, mult, toString を呼び出した際に値が変化している

単体テストの項目

テスト失敗時に「Results」の欄の左側に出る「test~」は、テストの項目名を表している。

項目名 詳細
Complex_0_0 new Complex(0.0, 0.0).toString()
Complex_1_0 new Complex(1.0, 0.0).toString()
Complex_0_1 new Complex(0.0, 1.0).toString()
Complex_1_1 new Complex(1.0, 1.0).toString()
Complex_M1_M1 new Complex(-1.0, -1.0).toString()
add_00 0 + 0
add_10 1 + 0
add_i0 i + 0
add_01 0 + 1
add_0i 0 + i
mult_scale2 (1 + 1i) * 2
mult_scale4 (1 + 1i) * 2 * 2
mult_scale8 (1 + 1i) * 2 * 2 * 2
mult_rol30 (1 + 0i) * (0.86602540378443864676372317075294 + 0.5i)1
mult_rol60 (1 + 0i) * (0.86602540378443864676372317075294 + 0.5i)2
mult_rol90 (1 + 0i) * (0.86602540378443864676372317075294 + 0.5i)3
mult_rol120 (1 + 0i) * (0.86602540378443864676372317075294 + 0.5i)4
mult_rol150 (1 + 0i) * (0.86602540378443864676372317075294 + 0.5i)5
mult_rol180 (1 + 0i) * (0.86602540378443864676372317075294 + 0.5i)6
mult_rol210 (1 + 0i) * (0.86602540378443864676372317075294 + 0.5i)7
mult_rol240 (1 + 0i) * (0.86602540378443864676372317075294 + 0.5i)8
mult_rol270 (1 + 0i) * (0.86602540378443864676372317075294 + 0.5i)9
mult_rol300 (1 + 0i) * (0.86602540378443864676372317075294 + 0.5i)10
mult_rol330 (1 + 0i) * (0.86602540378443864676372317075294 + 0.5i)11
mult_rol360 (1 + 0i) * (0.86602540378443864676372317075294 + 0.5i)12

課題1602

課題1601で作成した Complex クラスを実際に動作させるプログラム ComplexAction をパッケージ j2.lesson03 に作成しなさい。

このクラスには、mainメソッドだけを用意すればよい。

mainメソッドの動作を表す擬似コードは以下の通りである。出力する文字列などは以下の擬似コードに従うこと。

プログラム全体
    print "uの実数部を入力:"
    a = コンソール入力 (double)
    print "uの虚数部を入力:"
    b = コンソール入力 (double)
    print "vの実数部を入力:"
    c = コンソール入力 (double)
    print "vの虚数部を入力:"
    d = コンソール入力 (double)
    u = 複素数 (a + bi)
    v = 複素数 (c + di)
    print "足すと" + (u と v を足した結果).toString(), 改行
    print "掛けると" + (u と v を掛けた結果).toString(), 改行

結果の例

入力に順に 1.0, 2.0, 3.0, 4.0 を指定した場合、プログラムを終了まで実行した際のコンソールは以下のようになっている。

uの実数部を入力:1.0
uの虚数部を入力:2.0
vの実数部を入力:3.0
vの虚数部を入力:4.0
足すと4.0+6.0i
掛けると-5.0+10.0i

手順

指定した箇所で必ずテストを行うこと。

  1. 各メソッドに対する詳細な擬似コードを作成する
  2. パッケージ j2.lesson03 にクラス ComplexAction を作成
  3. クラス内に throws IOException の指定をした mainメソッド (public static void main(String[] args) throws IOException { }) を作成
  4. テスト項目「ComplexActionに対する骨格テスト」をパス
  5. 擬似コードをコメントとして貼り付ける
  6. mainメソッドを実装する
  7. 実際に実行して動作を確認する
  8. テスト項目 「ComplexActionに対する機能テスト」 をパス

テストの失敗メッセージ

骨格テスト

メッセージ 詳細
(クラス名), existence パッケージ内に課題で指定したクラスが存在していない。パッケージやクラス名を確認
(メソッド名), existence 指定されたメソッドが存在しない。名前や仮引数の型を確認
(メソッド名), public メソッドを作る際に public が抜けている
(メソッド名), static メソッドを作る際に static が抜けている
(メソッド名), type <T> メソッドを作る際に戻り値型の指定を間違っている。正しくは <T>
(メソッド名), throws ... メソッドを作る際に throws... の指定がない

機能テストの項目

テスト失敗時に「Results」の欄の左側に出る「test~」は、テストの項目名を表している。

項目名 詳細
sample 結果の例と同じ入力

課題1603 (optional)

アラームつきの24時間時計を表すクラス AlarmClock をパッケージ j2.lesson03 に作成しなさい。

演習では24時間時計を表すクラスを作成したが、ここではそれにさらにアラーム機能をつける。このアラーム機能は時刻をセットし、その時間を過ぎたりその時間になったりした際にアラームを鳴らす。

AlarmClock クラスはいくつかの private インスタンスフィールドと、1つの public コンストラクタ、3つの public インスタンスメソッドを持つ。

インスタンスフィールドはどのようなものを持ってもかまわない。ただし、すべて private で宣言すること。

コンストラクタは次のようなものを用意すること。これは public として指定すること。

インスタンスメソッドは次のものを用意すること。これらは public として指定すること。

また、実際にこのクラスを使用するための簡単な main メソッドを AlarmClock クラス内に用意すること。

手順

指定した箇所で必ずテストを行うこと。

  1. 各メソッドに対する詳細な擬似コードを作成する
  2. パッケージ j2.lesson03 にクラス AlarmClock を作成
  3. クラス内に必要なコンストラクタ、メソッドの骨格を作成
  4. テスト項目「AlarmClockに対する骨格テスト」をパス
  5. 擬似コードを各メソッドにコメントとして貼り付ける
  6. 擬似コードに対するプログラムを書く (main メソッド以外)
  7. 各メソッドをテストする (JUnitを用いてもよい)
  8. テスト項目 「AlarmClockに対する単体テスト」 をパス
  9. mainメソッドを実装する
  10. 実際に実行して動作を確認する

使用例

次のような main メソッドを書いた場合を考える。

// 23時15分
AlarmClock clock = new AlarmClock(23, 15, 0);
clock.show();

// 現在の時刻にセット
clock.setAlarmFor(23, 15, 0);
// アラームが鳴るはず

// 5時にセット
clock.setAlarmFor(5, 0, 0);

// 2時間進める
clock.tick(2 * 3600);
clock.show();

// 2時間進める
clock.tick(2 * 3600);
clock.show();

// 2時間進める
clock.tick(2 * 3600);
// アラームが鳴るはず
clock.show();

これを実行すると、次のように表示される。

23時15分0秒
アラーム
1時15分0秒
3時15分0秒
アラーム
5時15分0秒

テストの失敗メッセージ

骨格テスト

メッセージ 詳細
(クラス名), existence パッケージ内に課題で指定したクラスが存在していない。パッケージやクラス名を確認
(メソッド名), existence 指定されたメソッドが存在しない。名前や仮引数の型を確認
(メソッド名), public メソッドを作る際に public が抜けている
(メソッド名), static メソッドを作る際に static が抜けている
(メソッド名), type <T> メソッドを作る際に戻り値型の指定を間違っている。正しくは <T>

単体テスト

メッセージ 詳細
期待された結果と異なります (メソッド名)を起動した結果が期待された結果と異なる。テスト項目を参照
未初期化フィールド インスタンスを代入すべきフィールドにインスタンスを代入していない可能性がある

単体テストの項目

テスト失敗時に「Results」の欄の左側に出る「test~」は、テストの項目名を表している。

項目名 詳細
Ctor_000 new AlarmClock(0, 0, 0).show()
Ctor_123 new AlarmClock(1, 2, 3).show()
tick_000001 new AlarmClock(0, 0, 0).tick(1), show()
tick_000030 new AlarmClock(0, 0, 0).tick(30), show()
tick_000059 new AlarmClock(0, 0, 0).tick(59), show()
tick_000100 new AlarmClock(0, 0, 0).tick(60), show()
tick_003000 new AlarmClock(0, 0, 0).tick(1800), show()
tick_005959 new AlarmClock(0, 0, 0).tick(3599), show()
tick_010000 new AlarmClock(0, 0, 0).tick(3600), show()
tick_120000 new AlarmClock(0, 0, 0).tick(43200), show()
tick_235959 new AlarmClock(0, 0, 0).tick(86399), show()
tick_loop new AlarmClock(23, 59, 59).tick(1), show()
tick_maxint new AlarmClock(0, 0, 1).tick(1000000000).tick(1147483648), show()
alarm_0 new AlarmClock(0, 0, 0), setAlarmFor(0, 0, 0), show()
alarm_1a new AlarmClock(0, 0, 0), setAlarmFor(0, 0, 1), show()
alarm_1b new AlarmClock(0, 0, 0), setAlarmFor(0, 0, 1), tick(1), show()
alarm_1c new AlarmClock(0, 0, 0), setAlarmFor(0, 0, 1), tick(2), show()
alarm_f new AlarmClock(10, 0, 0), setAlarmFor(15, 0, 0), show()
alarm_fM1 new AlarmClock(10, 0, 0), setAlarmFor(15, 0, 0), tick(5 * 3600 - 1), show()
alarm_f0 new AlarmClock(10, 0, 0), setAlarmFor(15, 0, 0), tick(5 * 3600), show()
alarm_f1 new AlarmClock(10, 0, 0), setAlarmFor(15, 0, 0), tick(5 * 3600 + 1), show()
alarm_b new AlarmClock(22, 0, 0), setAlarmFor(7, 0, 0), show()
alarm_bM1 new AlarmClock(22, 0, 0), setAlarmFor(7, 0, 0), tick(9 * 3600 - 1), show()
alarm_b0 new AlarmClock(22, 0, 0), setAlarmFor(7, 0, 0), tick(9 * 3600), show()
alarm_b1 new AlarmClock(22, 0, 0), setAlarmFor(7, 0, 0), tick(9 * 3600 + 1), show()
alarm_nextM1 new AlarmClock(23, 59, 58), setAlarmFor(0, 0, 0), tick(1), show()
alarm_next0 new AlarmClock(23, 59, 58), setAlarmFor(0, 0, 0), tick(2), show()
alarm_next1 new AlarmClock(23, 59, 58), setAlarmFor(0, 0, 0), tick(3), show()

課題1604 (optional)

整数の集合を表すクラス IntegerSet をパッケージ j2.lesson03 に作成しなさい。

この整数の集合には 0 以上 100 以下の値を格納することができる。集合とは重複のない要素の集まりのことで、例えば {1, 2, 3} からなる集合に 5 を加えると {1, 2, 3, 5} となり、そこに 2 を加えると {1, 2, 3, 5} となる (2は重複するので変化しない)。

インスタンスフィールドはどのようなものを持ってもかまわない。ただし、すべて private で宣言すること。

コンストラクタは次のようなものを用意すること。これは public として指定すること。

インスタンスメソッドは次のものを用意すること。これらは public として指定すること。

また、実際にこのクラスを使用するための簡単な main メソッドを IntegerSet クラス内に用意すること。

手順

指定した箇所で必ずテストを行うこと。

  1. 各メソッドに対する詳細な擬似コードを作成する
  2. パッケージ j2.lesson03 にクラス IntegerSet を作成
  3. クラス内に必要なコンストラクタ、メソッドの骨格を作成
  4. テスト項目「IntegerSetに対する骨格テスト」をパス
  5. 擬似コードを各メソッドにコメントとして貼り付ける
  6. 擬似コードに対するプログラムを書く (main メソッド以外)
  7. 各メソッドをテストする (JUnitを用いてもよい)
  8. テスト項目 「IntegerSetに対する単体テスト」 をパス
  9. mainメソッドを実装する
  10. 実際に実行して動作を確認する

ヒント

このクラスの実装にはいくつかの方法があるが、すぐに思いつくであろう項目について紹介する。

内部表現 (フィールドの作り方) によってはこのクラスは簡単に作成できるが、逆に内部表現を間違えると非常に長いプログラムになってしまう可能性がある。

カプセル化の利点は、内部表現を隠蔽することによって、実際の挙動と内部表現が大きく違っても問題がなくなるため、より単純な実装をすることができるという点である。

int[] を使って集合を表現する場合

0,..,100 の数値を格納するために int 配列型を用いるという方法が挙げられる。

add を行う際に順番に配列に格納していくという方法が標準的であると思われる。

この方法では、add メソッドを次のように定義する。

public void add(int n) {
    ...
    this.set[size] = n;
    this.size++;
}

ただし、このクラスは「集合」を表すため、重複要素が入らないように注意する必要がある。重複要素を許してしまうと、この配列には無限個の整数を格納できる必要ができてしまう。

また、remove, contains メソッドでは、対象の数値が存在するかどうか検索する必要がある。

boolean[] を使って集合を表現する場合

より良い方法して、boolean 配列を用いるという方法が挙げられる。

この方法では、contains メソッドを次のように定義する (set は boolean[] 型のフィールド)。

public boolean contains(int n) {
    ...
    return this.set[n];
}

つまり、整数 n がある IntegerSet のインスタンス s に含まれている場合、s.set[n] = true で表す。逆に n が含まれていない場合は s.set[n] = false で表す。

このように定義すると、s.set の各要素は「存在する」か「存在しない」かのどちらかを表すだけになり、重複要素について特に難しく考える必要がなくなる

テストの失敗メッセージ

骨格テスト

メッセージ 詳細
(クラス名), existence パッケージ内に課題で指定したクラスが存在していない。パッケージやクラス名を確認
(メソッド名), existence 指定されたメソッドが存在しない。名前や仮引数の型を確認
(メソッド名), public メソッドを作る際に public が抜けている
(メソッド名), static メソッドを作る際に static が抜けている
(メソッド名), type <T> メソッドを作る際に戻り値型の指定を間違っている。正しくは <T>

単体テスト

メッセージ 詳細
期待された結果と異なります (メソッド名)を起動した結果が期待された結果と異なる。テスト項目を参照
未初期化フィールド インスタンスを代入すべきフィールドにインスタンスを代入していない可能性がある
範囲外のインデックスにアクセス 配列の範囲外のインデックスにアクセスしている。配列の初期サイズを確認
副作用があります union, intersection を呼び出した際に値が変化している

単体テストの項目

テスト失敗時に「Results」の欄の左側に出る「test~」は、テストの項目名を表している。

項目名 詳細
add_0 空の集合に対して add(0)
add_50 空の集合に対して add(50)
add_100 空の集合に対して add(100)
remove_0 {0,..,100} に対して remove(0)
remove_50 {0,..,100} に対して remove(50)
remove_100 {0,..,100} に対して remove(100)
contains_f0 空の集合に対して contains(0)
contains_f50 空の集合に対して contains(50)
contains_f100 空の集合に対して contains(100)
contains_t0 {0,..,100} に対して contains(0)
contains_t50 {0,..,100} に対して contains(50)
contains_t100 {0,..,100} に対して contains(100)
size_0 空の集合に対して size()
size_1 {0} に対して size()
size_34 {0,3,6,..,96,99} に対して size()
size_51 {0,2,4,..,98,100} に対して size()
size_101 {0,..,100} に対して size()
union_0_0 {} union {}
union_0_100 {} union {0,..,100}
union_100_0 {0,..,100} union {}
union_100_100 {0,..,100} union {0,..,100}
union_x2_x3 {0,2,4,..,98,100} union {0,3,6,..,96,99}
intersection_0_0 {} intersection {}
intersection_0_100 {} intersection {0,..,100}
intersection_100_0 {0,..,100} intersection {}
intersection_100_100 {0,..,100} intersection {0,..,100}
intersection_x2_x3 {0,2,4,..,98,100} intersection {0,3,6,..,96,99}
toIntArray_0 {}.toIntArray()
toIntArray_x1 {0,..,100}.toIntArray()
toIntArray_x2 {0,2,..,98,100}.toIntArray()
toIntArray_x3 {0,3,..96,99}.toIntArray()
ramdom ランダムに値を追加して、ランダムに値を削除して、toIntArray()