レポート課題

Java 7 または Java 6 を使って作りなさい。

レポートには表紙をつけてください。

変更履歴

2012/5/1
課題1初出
2012/5/1
解答用パッケージ名を kaitou → kotae に変更
2012/5/10
HandBuilderTest, TacticsTest, PlayerTest などを変更
2012/5/16
MyHandTest, Hand0 を変更
2012/5/29
課題1の PlayerTest.java を変更、課題2初出
2012/6/5
課題1の MyHandTest, Hand0, Tactics2Test を変更
2012/6/9
課題1の Tactics2Test の40億回のループを別テストに分割。 課題 2 の Node の next フィールドを public に変更。
2012/6/27
課題2の TDUListIteratorTest, TDUListIterator4Test, TDUListIterator5Test を変更
2012/7/11
課題1の MyHandTest を変更。そのため、 NiseClass, Gagyo, Chaki, Pagyo を追加
2012/9/19
課題2の TDUList2Test を変更。
2012/9/20
課題2の TDUListIterator3Test を変更。

課題2

提出先: レポートボックス

レポート締切日:
4年生
2012年6月13日20:00
2,3年生
2013年1月9日20:00

データをいくつでも入れられるリストクラス TDUList を作ります。 なお、各メソッドの細かい仕様はこちらが特に指定する以外は、Java7 または Java 6 の API マニュアルに準拠します。

各設問に付されているテストに関しては結果をレポートに添付すること。

なお、テストクラスを用意したので、各設問 において、指示のあるテストクラスを用いてテストを行い、正常であった旨を 報告しなさい。

なお、この課題も 課題ファイル集(9月20日版) を用意した。 インストール方法は課題1と同様である。

課題2-1

どんなオブジェクトでも一つ入れられるノードクラス kotae.Node<E> を作成しなさい。 線形リストに使用するため、内部に Node<E> 型の変数 next を public で持たせます。 作成すべきメソッドは次の通りです。

  1. 引数なしのコンストラクタ(オブジェクトも next も null が入る)
  2. オブジェクトを引数とするコンストラクタ(next には null が入る)
  3. E get() オブジェクトを取り出す
  4. void set(E arg) オブジェクトの上書き
  5. String toString() オブジェクトの文字列化

なお、test.NodeTest を使用して、正常であるかどうかを確かめること。

課題2-2

TDUList は Node オブジェクトの線形リストを内部に格納し、様々な操作を行 えるようにすることを目標とします。 以下の課題ではまず、文字列を入れられる線形リストのライブラリを作成し、 最終的に Generics を使用してどのようなオブジェクトも入れられるように改 良します。

さて、下記のクラス kadai.TDUList1 と kadai.TDUListIterator1はもっとも 基本的な雛形です。 TDUList1 において node メンバ変数は Node オブジェクトの線形リストの先 頭のオブジェクトを参照するためのメンバ変数です。 なお、常に線形リストの最後のオブジェクトは空の Node オブジェクトを持つ こととします。 この TDUList1 を継承し、Node オブジェクトの線形リストの要素数を数える size メソッドを実装したクラス kotae.TDUList2 を作成しなさい。 但し、空の Node オブジェクトだけの時は 0 を返し、値の入った Node オブ ジェクトが k 個連続したのちに空の Node オブジェクトがつながっている場 合は k を出力するようにしなさい。

なお、アルゴリズムの説明をする際は、データ例を図示し、数えている対象と 終了条件がわかるように印を付けなさい。 なお、 test.TDUList2Test を利用し、テスト結果も示すこと。

kadai.TDUList1


package kadai;
import java.util.AbstractSequentialList;
import java.util.ListIterator;
import kotae.Node;
public class TDUList1 extends AbstractSequentialList<String> {
	public Node<String> node;
	public TDUList1(){
		super();
		node = new Node<String>();
	}
	@Override
	public ListIterator<String> listIterator(int arg) {
		return new TDUListIterator1(node,arg);
	}
	@Override
	public int size() {
		return 0;
	}
}

kadai.TDUListIterator1


package kadai;
import java.util.ListIterator;
import java.util.NoSuchElementException;

import kotae.Node;
public class TDUListIterator1 implements ListIterator<String> {
	public TDUListIterator1(Node<String> node, int arg) {
	}
	@Override
	public void add(String e) {
		throw new UnsupportedOperationException();
	}
	@Override
	public boolean hasNext() {
		return false;
	}
	@Override
	public boolean hasPrevious() {
		return false;
	}
	@Override
	public String next() {
		throw new NoSuchElementException();
	}
	@Override
	public int nextIndex() {
		return 0;
	}
	@Override
	public String previous() {
		throw new NoSuchElementException();
	}
	@Override
	public int previousIndex() {
		return -1;
	}
	@Override
	public void remove() {
		throw new UnsupportedOperationException();
	}
	@Override
	public void set(String e) {
		throw new UnsupportedOperationException();
	}
}

課題2-3

kadai.TDUList3 を下記のようにします。 このクラスが機能するように kadai.TDUListIterator1 クラスを継承した kotae.TDUListIterator3 クラスを作成し、kotae.Node<String> と int の値 を引数に持つコンストラクタと String オブジェクトを引数にする add メソッ ドを実装しなさい。 但し、このコンストラクタの int の値の意味は、java.util.ListIterator の APIマニュアルの通りで、与えられた int の値にカーソルをセットします。 そして、 add メソッドでカーソルの位置に要素を追加しなさい。 これに対して test.TDUList3Testとtest.TDUListIterator3Test を活用しなさい。

この add メソッドはどのようにデータが加わっていくかを図示してください。 また、 TDUListIterator3 の add メソッドと、 TDUList3 の add メソッドの 関わりについて考察しなさい。

kadai.TDUList3


package kadai;
import java.util.ListIterator;
import kotae.TDUList2;
import kotae.TDUListIterator3;
public class TDUList3 extends TDUList2 {
	public TDUList3(){
		super();
	}
	public ListIterator<String> listIterator(int arg0){
		return new TDUListIterator3(node,arg0);
	}
}

課題2-4

kadai.TDUList4 を下記のようにします。 この時、kotae.TDUListIterator3 を継承し、 public String next() と public boolean hasNext() メソッドを 実装したクラス kotae.TDUListIterator4 を作成しなさい。 これに対しては、 test.TDUList4Testと test.TDUListIterator4Test を 活用しなさい。

TDUList4


package kadai;
import java.util.ListIterator;
import kotae.TDUListIterator4;
public class TDUList4 extends TDUList3 {
	public TDUList4() {
		super();
	}
	@Override
	public ListIterator<String> listIterator(int arg){
		return new TDUListIterator4(node,arg);
	}
}

課題2-5

kadai.TDUList5 を下記のようにします。 このとき、 kotae.TDUListItrator4 を継承し、set メソッドを実装したクラス kotae.TDUListIterator5 を作成しなさい。 これに対しては、 test.TDUList5Testとtest.TDUListIterator5Testを活用し なさい。 なお、親メソッドと同等のことをやろうとする場合において、プログラムのコ ピーを避けてください。

TDUList5


package kadai;
import java.util.ListIterator;
import kotae.TDUListIterator5;
public class TDUList5 extends TDUList4 {
	public TDUList5() {
		super();
	}
	@Override
	public ListIterator<String>> listIterator(int arg){
		return new TDUListIterator5(node,arg);
	}
}

課題2-6

  1. 以上をまとめ、Generics を使用し、任意のオブジェクトを格納できるリスト クラス kotae.TDUList<E> と kotae.TDUListIterator<E> を作成 しなさい。 なお、テストとして test.TDUListTest と test.TDUListIteratorTest を活用 しなさい。
  2. 次に、次の kadai.Keisoku プログラムをそれぞれのリストに対して10回動作させ、 kotae.TDUList と java.util.LinkedList と java.util.ArrayList のそれぞ れの各部分の実行時間の比較をしなさい。
  3. なお、この課題において想定されるのは、add のみ TDUList が極端になり、 他のテストは大体同じ程度の速度になるということである。 もし、他のテストも遅くなる場合は、根本的にアルゴリズムが不必要に遅く作 成されているためなので、不合格とする。 特に add や next を一回やる毎にリストの先頭から目的の要素まで数え上げ るようなプログラムは不可である。 また、この課題2-6だけ高速なアルゴリズムで、2-2から2-5までが遅いアルゴ リズムを採用する事も認めない。
  4. さて、作成したメソッドがどのように呼び出されるかを分析し、アルゴリズム のどこに問題があるのかを指摘しなさい。 さらに、改善方法のアイディアを述べなさい。

kadai.Keisoku


package kadai;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
class Keisoku {
	public static void main(String[] args) {	
		switch((int) (Math.random()*3)){
		case 0: test(new kotae.TDUList<Integer>());
		break;
		case 1: test(new LinkedList<Integer>());
		break;
		case 2: test(new ArrayList<Integer>());
		break;
		}
	}
	private static void test( List<Integer> list) {
		int max=10000;
		StopWatch sw = new StopWatch();	
		for(int i=0;i<max;i++){
			list.add(new Integer((int) (Math.random()*1000)));
		}
		System.out.println(sw);
		for(int i=0;i<100;i++){
			list.set((int) (Math.random()*max),new Integer((int) (Math.random()*1000)));
		}
		System.out.println(sw);
		double average=0;
		for(Integer i : list){
			average+=i;
		}
		average/=max;
		System.out.println(sw);
		System.out.println(average);
		System.out.println(list.getClass().getName());
	}
}

kadai.StopWatch


package kadai;
import java.util.Date;
public class StopWatch {
	private Date now;
	public StopWatch() {
		now = new Date();
	}
	@Override
	public String toString() {
		final Date next = new Date();
		double result = (double)( next.getTime()-now.getTime())/1000;
		now = next;
		return String.valueOf(result);
	}
	
}

課題1

4年生
2012年5月23日20:00
2,3年生
2012年11月29日20:00(レポートボックスは28日になってましたが訂正しました)

提出先: 2号館レポートボックス

じゃんけんを行うことを考えます。 プレイヤー二人が対戦をするのですが、それぞれが戦略を考えて手を出します。

以下の設問において kotae パッケージを作り、 その中に指定されたクラスを作成しなさい。

なお、テストクラスを用意したので、各設問 において、指示のあるテストクラスを用いてテストを行い、正常であった旨を 報告しなさい。

課題1-1

始めに、「手」を表現するクラスを作ります。 interface Hand を下記のように定義します。

kadai/Hand.java


package kadai;
public interface Hand {
	int wins(Hand hand);
	int getNum();
}

作成する kotae/MyHand クラスは、 コンストラクタにint型の値を入れると、getNum() でその値が取り出せるとします。 手は「Yasumi」「Gu」「Choki」「Pa」の4種類とし、この順に 0,1,2,3 と対 応するとします。 また、 toString() をオーバライドし、上記の Yasumi, Gu, Choki, Pa を文 字列として取り出せるとします。 さらに、 equals(Object) は同じ手ではtrue となるようにオーバライドし、 hashCode() は同じ手で同じ値が出るようにオーバライドします。

wins メソッドは引数の手に対して、勝ちなら 1, あいこなら 0, 負けなら -1 を返すように実装します。 Yasumi の時は、相手が Yasumi 以外なら負けで、 Yasumi ならあいことしま す。

YasumiGuChokiPa
Yasumi0-1-1-1
Gu101-1
Choki1-101
Pa11-10

なおテストには test/MyHandTest.java を使用してください。

課題1-2

作成した MyHand クラスに対して、手を出すことを抽象化します。 kadai.HandBuilder は getHand に0から 3 を指定すると、対応するHand のオブジェク トを返すためのインタフェースです。 これを素朴に実装したのが kadai.HandBuilder1 で、これは単純に毎回 MyHand オブジェクトをコンストラクタを使用して生成します。

kadai/HandBuilder.java


package kadai;
public interface HandBuilder {
	Hand getHand(int type);
}

kadai/HandBuilder1.java


package kadai;
import kotae.MyHand;
public class HandBuilder1 implements HandBuilder {
	public HandBuilder1(){}
	@Override
	public Hand getHand(int type) {
		return new MyHand(type);
	}
}

さて、毎回コンストラクタを使用せず、getHand で同じ手では同じオブジェク トを返す kotae.HandBuilder2 を作りなさい。

なお、これらをテストするクラス test.HandBuilder2Test を使用しなさい。

課題1-3

次に、手を出す戦略のクラスを作ります。 kadai/Tactics インタフェースは getHand メソッドがありますが、引数はあ りません。 getHand では戦略にしたがった手が得られます。 ただし、コンストラクタは HandBuilder を得て、手はこの HandBuilder 経由 で出します。

kadai/Tactics.java


package kadai;
public interface Tactics {
	Hand getHand();
}

さて、このような前提で、次の戦略を実現する kotae.Tactics1, kotae.Tactics2 クラスを作りなさい。

kotae.Tactics1
Gu, Choki, Pa を等確率でランダムに出す
kotae.Tactics2
Gu, Choki, Pa を順番に出す

なお、これらをテストするクラス test.Tactics1, test.Tactics2 を使用しな さい。

なお、計算がオーバフローする可能性のあるプログラムは不可とする。

課題1-4

kadai.Janken は、二つの Player オブジェクトを与えて、 pon メソッ ドでじゃんけんをさせるクラスである。 この Player クラスは、コンストラクタで名前を受け取り、さらに setTactics メソッドで Tactics を一つ持つことができる。 さて、Janken 中の pon メソッドでは、Player オブジェクトに対して getHand メソッドで手を出させ、勝敗を判定し、Player オブジェクトに対し て win, loose, draw メソッドで勝負の結果を与える。

Player オブジェクトはさらに toString メソッドをオーバライドしていて、 次のフォーマットで勝敗を文字列として出力する。

名前":\t"勝数" wins, "負け数" looses and "引分数" draws."

次の kadai.Janken を上記のように動作させるように kotae.Player クラス を作成しなさい。

kadai/Janken.java


package kadai;
import kotae.Player;
public class Janken {
	private Player[] players;
	public Janken(Player player0, Player player1){
		players = new Player[2];
		players[0] = player0;
		players[1] = player1;
	}
	public Hand[] pon(){
		Hand[] hands = new Hand[2];
		for(int i=0; i<2; i++){
			hands[i] = players[i].getHand();
		}
		switch(hands[0].wins(hands[1])){
		case 0:
			players[0].draw();
			players[1].draw();
			break;
		case 1:
			players[0].win();
			players[1].loose();
			break;
		case -1:
			players[0].loose();
			players[1].win();
			break;
		}
		return hands;
	}
}

課題1-5

omake.Nichiyou を実行すると、ランダムで HandBuilder1 と HandBuilder2 から一つを選んで二人の Player を100万回勝負をさせ、 その実行結果と実行時間を表示する。 これを HandBuilder1 と HandBuilder2 で 10 回ずつ計測し、平均値を比較し なさい。 さらに、この実行時間が異なる理由を説明しなさい。

omake/Nichiyou.java


package omake;
import java.util.ArrayList;
import kadai.Hand;
import kadai.HandBuilder;
import kadai.HandBuilder1;
import kadai.Janken;
import kotae.HandBuilder2;
import kotae.Player;
import kotae.Tactics1;
import kotae.Tactics2;
public class Nichiyou {
	public static void main(String[] args) {
		HandBuilder[] hb = {new HandBuilder1(), new HandBuilder2()};
		taisen(hb[(int) (Math.random()*hb.length)]);
	}
	private static void taisen(HandBuilder hb) {
		StopWatch sw = new StopWatch();
		Player curepiece = new Player("CurePiece");
		curepiece.setTactics(new Tactics1(hb));
		Player sazae = new Player("Sazae");
		sazae.setTactics(new Tactics2(hb));
		Janken nichiyou = new Janken(curepiece, sazae);
		ArrayList<Hand[]> kekka = new ArrayList<Hand[]>();
		for(int i=0; i<1000000; i++){
			kekka.add(nichiyou.pon());
		}
		System.out.println(sw);
		System.out.println(hb.getClass().getName());
		System.out.println(curepiece);
		System.out.println(sazae);
	}
}

発展課題1-6

課題1-5の計測が完了するまでに実行する回数の平均値を理論的に求めなさい。

おまけ

Eclipse のセッティングの方法

  1. Java のプロジェクトを作成する
  2. 右クリックで properties を選択し、Text file encoding を UTF-8 にする。
  3. 課題ファイル集(7月11日版) をダウンロードする。
  4. プロジェクトを右クリックし、import を選ぶ
  5. General の中の Archive file を選ぶ
  6. ダウンロードしたファイルを選択すると、 src の中にファイルが取り込 まれる
  7. test.MyHandTest の画面の org.junit の波線付近にカーソルを当て、 fix Project setup を選ぶと、Add JUnit4 Library ... の表示が出るので、 Ok を押す

なお、問題訂正などでプログラムが変更になった場合、上記の最新のファイル 集をダウンロードした後で、作業中のプロジェクトでもう一度importして下さ い。 上書きするファイルが検出されるとダイアログが表示されますが、 Yes ALL で問題だけ最新の状態になるはずです。

採点基準

  1. 問題をちゃんと理解すること。 特に解答が楽になるようにとか、「わかりやすく」とか「しっかり」など、非 科学的な理由で勝手に問題を作り替えてはいけません。
  2. プログラムが正常に動作すること。 指定した出力を正確に出すこと。 また、指定していない表示を行わないこと。 さらに、多少の動作条件の変化に対して、正常に対応できること。 特に、テストに対しては正常に動作しても、考えうる他の正常な入力に対して暴走するようなプログラムは不可です。
  3. 実行例を付けること。 実行可能なプログラムに関して、正しく実行された実行例を必ずつけること。 なお、問題の趣旨を理解しており、膨大な出力のうち、全ての出力が必ずしも 必要ないと判断される場合は、出力の一部を省略しても良い。
  4. 説明が適切であること。 プログラムの内容を正しく説明していなければなりません。 また、テストの内容を理解し、テスト結果からプログラムが正常であることを 理由を付けて判定すること。

レポート作成上の注意点

  1. 表紙をつけてください。 また、再提出時は前回の表紙をつけてください。
  2. プログラミングのレポートでは必ずプログラムの説明をすること。 その時に、一行一行を日本語に直訳するのではなく、データの読み込みとか、 出力とかの部分に分割し、機能毎に使用した手法を説明すること。 プログラム中にコメントを入れてもプログラムの説明とはみなさないので注意 する事。 プログラムの説明はつぎのように説明をしてください。
    1. プログラム全体の構成(どんなフィールドやメソッドを作成したか)
    2. 各部分の機能(メソッド毎に説明すること)
    3. それぞれの機能を実現するために行ったプログラミング上の工夫(特に複雑なアルゴリズムは図示すること)
  3. 「問題を解きなさい」という問に対して「解きました。合ってました」で は正解ではないことはわかるはず。 「テストしなさい」という問に対しては、テストの方法の説明、実際のテスト の実施方法、テスト結果、検証などを説明して下さい。
  4. レポートは手書きでもワープロでも構いません。但し、実行結果はコン ピュータの出力を添付すること。 また、なるべく白黒で作成すること。実行結果などでどうしても色が付いてしまうような場合はそのままで構いません。 実行結果が無いレポートは不合格です。
  5. 考察は必ず書いて下さい。
  6. 不必要なことはなるべく書かない事。 長過ぎるレポートは減点します。またなるべく両面印刷にしてください。 但し、文字は必要以上に小さくしない事。レポート本文の文字は 10 ポイント 以上のものを使う事。

なお、写したと思われるほど酷似したレポートが複数提出された場合、原著が どれかの調査を行わず、抽選で一通のレポートのみを評価 の対象とし、他は提出済みの不合格レポートとして再提出は課しません。 自分で意図せずに他人にコピーされてしまった場合も同様ですので、レポート の取り扱いについては十分に注意して下さい。


坂本直志 <[email protected]>
東京電機大学工学部情報通信工学科