2/2 まとめ


質問と回答


1/19の課題

課題問題

chat(おしゃべり) サーバと接続して,おしゃべりに参加できる java ク ライアントプログラムを作成しなさい.

ヒント
以下の2つのスレッドを作成するとシンプルに作成できます.

これだと,入力中に他の人からの入力があると画面が乱れるなどの欠点があり ますが,とりあえずは気にしないで下さい.GUI等を使って凝る必要はありま せん.
解答例
  // 入出力ストリームを使うので,java.io.* を import
import java.io.*;
  // ソケットを使うので java.net.* を import 
import java.net.*;

// ユーザのキーボード入力を受け取って, サーバに送るスレッドと 
// サーバと接続したソケットからメッセージを受け取って表示するスレッド
// を共用したクラス
class Sender implements Runnable{
  // 入力ストリーム
  BufferedReader in;
  // 出力ストリーム
  PrintWriter out;
  // コンストラクタ
  public Sender(BufferedReader in,PrintWriter out){
    // 左辺の this.inはインスタンス変数の in, 右辺の inは引数の in
    this.in=in;
    this.out=out;
  }
  // スレッドが start すると,これが呼ばれて,動き続ける.
  public void run(){
    try{
      String s;
      // 入力ストリームから一行入力
      while((s=in.readLine())!=null){
        // 出力ストリームに一行出力
	out.println(s);
      }
    }
    catch(Exception e){
      System.err.println(e);
    }
  }
}
// java ChatClient ホスト名 ポート番号
// と呼び出す
class ChatClient{
  public static void main(String args[]){
    // ホスト名
    String hostName=args[0];
    // ポート番号を文字列から整数に変換
    int port=Integer.parseInt(args[1]);
    try{
      // サーバと接続するためのソケットを作る
      Socket sock=new Socket(hostName,port);
      // ユーザからの入力のストリームの作成
      BufferedReader ui=new BufferedReader(new InputStreamReader(System.in));
      // サーバからの入力のストリームの作成
      BufferedReader si=new BufferedReader(new InputStreamReader(sock.getInputStream()));
      // サーバへの出力のストリームの作成
      PrintWriter so=new PrintWriter(sock.getOutputStream(),true);
      // ユーザへの出力のストリームの作成
      PrintWriter uo=new PrintWriter(System.out,true);
      // ユーザ入力をサーバに転送するスレッドを作成し, start
      new Thread(new Sender(ui,so)).start();
      // サーバからの入力をユーザに転送するスレッドを作成し, start
      new Thread(new Sender(si,uo)).start();
    }
    catch(IOException e){
      System.err.println(e);
    }
  }
}

講評

上の例では,送信用スレッドと受信用スレッドに対応するクラスをSenderとい う一つのクラスにしたが,そのために System.out から PrintWriterを作ると いうこれまで使っていないテクニックを使っている.多くの解答は二種類のク ラスを作ったが,そちらの方が自然と言える.

このプログラムはあらかじめ,サーバを動かしておいてから実行する必要が あるが,その説明を読んでないもの,自分の作成したプログラムなのに実行時 の引数がわかっていないもの,実際に実行したとは思えない解答もあった.

変わった解答例としては,入力をSystem.inから行わずに Frameの中の TextFieldから行うことで,スレッドを作らずに実現しているものもあった.


1/26の課題

SortTest3 でなぜソートが行われるかを示した上で,比較回数が O(n * log n)となることを示しなさい.厳密な証明でなくて,概略で構わない.
解答の概略
なぜソートが行われるか?
  1. ソート済みの配列2つを入力としてmergeによりソート済みの配列が作られることを示す.
  2. sortを再帰的に呼ぶプロセスが停止することを示す.
比較回数が O(n*log n)
  1. 長さ m の2つの配列をマージする際の比較回数が 2*m 以下であることを示す
  2. sort(長さn配列) の 比較回数が 「2* sort(長さn/2の配列)の比較回数+ 長さ n/2の配列をマージする際の比較回数」であることを示す.
  3. 2mの長さの配列をソートする際の比較回数がおよそ, m*2mであることを示す.

最終課題問題


1勝負50回の繰り返しじゃんけんをプレイするプログラムを作れ. ただし,弱い相手に対しては大幅に勝ち越すものでなくてはいけない.

詳細

オプション課題


プロトコル(1行単位)

1. 「ユーザ名の登録」
サーバ -> クライアント: Name:
クライアント -> サーバ: ユーザ名(ここで嘘をつくと,勝っても正しく記録されない)
2. 「ゲームのスタート」
サーバ -> クライアント: Start 対戦相手
3. 「出す手を伝える」
クライアント -> サーバ: 手(0,1,2)
サーバ -> クライアント: 相手の手
4. 50回の勝負が終わる前は3に戻る.
5. 結果の表示
サーバ -> クライアント : 
ヒント

プロトコルを理解するために,

telnet ux019 3331
とやって,名前の入力,[0-2]の数字の入力をやってみるのも良い.
サンプルクライアントプログラム
// Randomクラスを利用するので java.util.*をインポート
import java.util.*;
// 入出力関係のクラスを利用するので java.util.*をインポート
import java.io.*;
// ネットワーク関係のクラスを利用するので java.net.*をインポート
import java.net.*;

class Kodomo{
  static void janken(BufferedReader in,PrintWriter out) throws IOException{
    Random r=new Random();
    String s=in.readLine();
    System.out.println("対戦相手は"+s);
    int index=(int)(r.nextDouble()*3.0);
    for(int i=0;i< 50;i++){
      out.println(index);
      // サーバからの入力をもらう
      if((s=in.readLine())==null){
	System.exit(1);
      }
      index=(Integer.parseInt(s)+2)%3;
    }
    while((s=in.readLine())!=null)
      System.out.println(s);
  }
  public static void main(String args[]) throws IOException{
    if(args.length<2) usage();
    Socket sock=new Socket(args[0],Integer.parseInt(args[1]));
    BufferedReader in=new BufferedReader(new InputStreamReader(sock.getInputStream()));
    PrintWriter out=new PrintWriter(sock.getOutputStream(),true);
    out.println(System.getProperty("user.name"));
    janken(in,out);

  }
  static void usage(){
    System.err.println("使い方: java Janken ホスト名 ポート番号");
    System.exit(1);
  }
}
相手の直前に出した手に勝てる手を出す(0に勝つのは2,1に勝つのは0,2に勝 つのは1だが,これは 相手の出した手に2を足して 3 で割った余り)プログラム. プロトコルが決まっているので,スレッドを使う必要はない.
Otona2のプログラムは以下の通り
// Randomクラスを利用するので java.util.*をインポート
import java.util.*;
// 入出力関係のクラスを利用するので java.util.*をインポート
import java.io.*;
// ネットワーク関係のクラスを利用するので java.net.*をインポート
import java.net.*;

class Otona2{
  static void janken(BufferedReader in,PrintWriter out) throws IOException{
    Random r=new Random();
    String s=in.readLine();
    System.out.println("対戦相手は"+s);
    // 1手目は乱数で 0-2を生成する
    int index=(int)(r.nextDouble()*3.0),index1;
    // ゲームの記録を取る
    int[][] rec=new int[2][50]; 
    for(int i=0;i< 50;i++){
      out.println(index);
      // サーバからの入力をもらう
      if((s=in.readLine())==null){
	System.exit(1);
      }
      // 相手の出した手を整数に直す
      index1=Integer.parseInt(s);
      // 記録する
      rec[0][i]=index;
      rec[1][i]=index1;
      // 2手目は相手の1手目に勝つものを出す.
      if(i==0) index=(index1+2)%3;
      else{
	int hist[]=new int[3];
	// 過去に,1手前の自分の手,相手の手と一致する回の次に相手が何を
        // 出したかの頻度を計算する.
	for(int j=0;j< i;j++){
	  if(rec[0][j]==rec[0][i] && rec[1][j]==rec[1][i])
	    hist[rec[1][j+1]]++;
	}
	// 同じ状況で相手が0を多くだしていた時は2を出す
	if(hist[0]>=hist[1] && hist[0]>=hist[2]) index=2;
	// 同じ状況で相手が1を多くだしていた時は0を出す
	else if(hist[1]>=hist[2]) index=0;
	// 同じ状況で相手が2を多くだしていた時は1を出す
	else index=1;
      }
    }
    while((s=in.readLine())!=null)
      System.out.println(s);
  }
  public static void main(String args[]) throws IOException{
    if(args.length< 2) usage();
    Socket sock=new Socket(args[0],Integer.parseInt(args[1]));
    BufferedReader in=new BufferedReader(new InputStreamReader(sock.getInputStream()));
    PrintWriter out=new PrintWriter(sock.getOutputStream(),true);
    out.println(System.getProperty("user.name"));
    janken(in,out);

  }
  static void usage(){
    System.err.println("使い方: java Otona2 ホスト名 ポート番号");
    System.exit(1);
  }
}

課題をクリアした人のリスト(クリアしたつもりなのに含まれていない人は申し出てください)

g840587
g840593
g920024
g940029
g940124
g940151
g940186
g940199
g940229
g940250
g940288
g940299
g940347
g940349
g940351
g940566
g940573
g940578
g940600
g940604
g940620
g940622
g940645
g940795
g940811
g940821
g940845
g940846
g940851
g940859
g940868
g940869
g940889
g940893
g941042
g941059
g941140
g941267
g950060
g950112
g950134
g950171
g950241
g950245
g950433

Kodomo{1,2,3}, Otona{1,2}すべてを相手に 61点以上を取った人

g940029
g940299
g940349
g940573
g940578
g940645
g940893
g950112
g950433