10/19レポート講評


問題

以下のプログラムの(1)-(7)を埋めて,簡単な数当てゲームで遊ぶためのプログラムを完成させなさい.
  1. プログラムはまず1から10000までの隠した数(整数)を作成する
  2. 人はその数を推測してキーボードから入力する(1から10000までの数でなくてはいけない).
  3. プログラムは人が入力した数が,隠していた数と一致した時は, 何回目で成功したか答える.
  4. そうでない時は,以下のように答える.
    VERY LOW
    隠した数の二分の一以下の数が入力された時
    LOW
    隠した数より小さく,二分の一より大きい数が入力された時
    HIGH
    隠した数より大きく,二倍以下より小さい数が入力された時
    VERY HIGH
    隠した数の二倍以上の数が入力された時
  5. 2から繰り返す.
// 学生証番号: XXXXXXX
// 名前: XXXXXX
import java.io.*; // 入力に関するクラスを使う時は必要
import java.util.*; // Randomを使うので必要

class Kadai1019 {
   // throws IOException で内部で入出力エラーが起きる可能性があることを示す
    public static void main(String[] args) throws IOException{
	System.out.println("Guess a number");
  // 入力をするためには,System.inからBufferedReaderを作らなくてはいけない
	BufferedReader d=new BufferedReader(new InputStreamReader(System.in));
	// Randomクラスのオブジェクトを作る
	Random r=new Random();
	// answer に1から10000までの乱数を入れる
	int answer=  (1)  ;
	// 回答回数を記録する変数
	int count=1;
	for(;;){
	// キーボードからの一行読み込み
	    String s=d.readLine();
	    // String(文字列)から整数への型変換
	    int val=  (2)  ;
	    // 正解の時はfor文を終了
	    if(  (3)  ) break;
	    // 正解よりも小さい時
	    if(  (4)  ){
		// 2倍しても正解以下時
		if(  (5)  ){
		    System.out.println("VERY LOW");
		}
		else {
		    System.out.println("LOW");
		}
	    }
	    else{ // 正解よりも大きい時
		// 正解の2倍以上の時
		if(  (6)  ){
		    System.out.println("VERY HIGH");
		}
		else{
		    System.out.println("HIGH");
		}
	    }
	    // countを1増やす 
	      (7)  ;
	}
	System.out.println(count);
    }
}

講評


解答例とコメント

// 学生証番号: XXXX
// 名前: XXXX
import java.io.*; 
import java.util.*; 

class Kadai1019 {
    public static void main(String[] args) throws IOException{
        System.out.println("Guess a number");
        BufferedReader d=new BufferedReader(new InputStreamReader(System.in));
        Random r=new Random();
        int answer=r.nextInt(10000)+1;
        int count=1;
        for(;;){
            String s=d.readLine();
            int val=Integer.parseInt(s);
            if(val==answer) break;
            if(val< answer){
                if(val*2<=answer){
                    System.out.println("VERY LOW");
                }
                else {
                    System.out.println("LOW");
                }
            }
            else{ 
                if(answer*2<=val){
                    System.out.println("VERY HIGH");
                }
                else{
                    System.out.println("HIGH");
                }
	    }
	    count=count+1;
	}
        System.out.println("count="+count);
    }
}
穴埋めなので,ほとんどの人がこれと同じ解答を書いてくれました.

オプション課題

このゲームの解答側のプログラムを作ってみること.1-10000までのそれぞれが正解であるケースの平均回答回数をなるべく少なくするプログラムに挑戦するのも面白いだろう. なお,
class Option1019{
  public static void main(String[] args){
    for(int i=1;i<=10000;i++)
      System.out.println(i);
  }
}
というのは,冗談としては面白いが,解答としては認めない.

講評


解答例(1)

/*
XXXXX XXXX
数当てゲームの解答側のプログラムです.
少なくとも,最初に質問する数が2500以下のとき質問回数の平均が最小とならなければ,
このプログラムでの質問回数の平均(11.1625回)が最小になります.
*/
import java.io.*;

class Option1019{
    public static void main(String[] args){
	int max = 10000;
	int answer=  0  ;
	int[] HL;
	HL = new int[max];
	int[] VHL;
	VHL = new int[(max+1)];
	int[] klog;
	klog = new int[max+1];
	HL[0] = 0;
	HL[1] = 1;
	HL[2] = 3;
	VHL[0] = 0;
	VHL[1] = 1;
	VHL[2] = 3;
	klog[1] =1;
	klog[2] =2;
	for(int i=3;i< max;i++){
	    if(i%2==0){
		HL[i]=HL[(i/2)] +HL[(i/2 -1)]+i;
	    }
	    else{
		HL[i]= 2*HL[((i-1)/2)] +i;
	    }
	}
	for(int j=3;j<=max;j++){
	    int min=(int)(Math.round(Math.log(max) /Math.log(2)) +1)*max;
	    for(int k=1;k<=j;k++){
		if(k==1){
		    VHL[j]=VHL[j-1]+j;
		}
		else if(k==2){
		    VHL[j]=HL[Math.max(0,(j- 2*k +1))]+j+2;
		}
		else{
		    VHL[j]=VHL[(k/2)]+HL[(k-(k/2)-1)]+HL[Math.min((k-1),(j-k))]+HL[Math.max(0,(j- 2*k +1))]+j;
		}
		if(min > VHL[j]){
		    min=VHL[j];
		    klog[j]=k;
		}
	    }
	    VHL[j]=min;
	    //	System.out.println("VHL["+ j +"]"+ VHL[j]);
	    //	System.out.println("klog["+ j +"]"+ klog[j]);
	}
	System.out.println("minimum average of "+ max +" = "+ (double)VHL[max]/max);
	//ここから実行部
	System.out.println("Decide a number (1~"+max+")");
	System.out.println("Please answer 'H'(if 'number'>=2*'your number') ,");
	System.out.println("'h'(if 'your number' < 'number' < 2*'your number')");
	System.out.println("'B'(if 'number' = 'your number')");
	System.out.println("'l'(if 'your number'/2 < 'number' < 'your number')");
	System.out.println("'L'(if 'number' < your number)");
	BufferedReader d=new BufferedReader(new InputStreamReader(System.in));
	String str;
	int minimum=1;
	int maximum=max;
	char Ans=' ';
	int count=0;
  	boolean flag=true;
	int k=0;
	while(true){
	    if(maximum< 1||minimum>max||minimum>maximum){
		flag=false;
		break;
	    }
	    if(minimum==1){
		k=klog[maximum];
	    }
	    else{
		k=minimum+(maximum-minimum)/2;
	    }
	    System.out.print("Your Number is "+k+" ?"+" ->");
	    try{
		str = d.readLine();
		Ans = str.charAt(0);
	    }
	    catch(Exception e){
		continue;
	    }
	    if(Ans == 'L'){
		count++;
		minimum=2*k;
	    }
	    else if(Ans == 'H'){
		count++;
		maximum= (k/2);
	    }
	    else if(Ans == 'l'){
		count++;
		maximum=Math.min(maximum,2*k-1);
		minimum=k+1;
	    }
	    else if(Ans == 'h'){
		count++;
		maximum= k-1;
		minimum=Math.max(minimum,(k/2)+1);
	    }
	    else if(Ans == 'B'){
		count++;
		break;
	    }
	    else{
		System.out.println("Prease answer 'H'(if 'number'>=2*'your number') ,");
		System.out.println("'h'(if 'your number' < 'number' < 2*'your number')");
		System.out.println("'B'(if 'number' = 'your number')");
		System.out.println("'l'(if 'your number'/2 < 'number' < 'your number')");
		System.out.println("'L'(if 'number' < your number)");
	    }			
	}
	if(flag){
	    System.out.println(k+" is O.K. "+"count="+count);
	}
	else{
	    System.out.println("You must have made mistakes.");
	}
    }
}
HIGHとLOWだけしか区別がない場合は,最小回数を実現するプログラムを作る のは容易(正解の可能性のある区間の中間を質問に選ぶ)なのですが,VERY HIGH, VERY LOWがあると,表を作って解く必要がありますね.平均質問回数 11.1625回 は最小のはずです.

解答例(2)

/*学生証番号:XXXXX
  名前:XXXX

  この課題の場合、最初のプレイヤー側の回答によって、答えのあり得る範囲は四つに分けられる。
  また、Very High と返され続けた場合のみも、四つに分けられる.
  この場合、答えとしてありうる数字のうちどことの高低をきけば要素数の分散が
  一番小さくなるかを求めると、要素のうち下から数えて4/11の位置にある数字であると求められたので、
  それをもとに聞くようにプログラムした。
  また、それ以外の場合は High or Low しかないので、1/2の位置の数字を聞けば良い。
*/
import java.io.*;

class Kadai1019opt{
    static boolean check(String str,String ch1,String ch2){
	int N=str.indexOf(ch1);
	int M=str.indexOf(ch2);
	if(N==-1 && M==-1){return false;}else{return true;}
    }
    public static void main(String[] args)throws IOException{
	System.out.println("Decide a number (1~10000)");
	System.out.println("Prease answer 'Very High'(if 'number'>=2*'your number') ,");
	System.out.println("'High'(if 'your number' < 'number' < 2*'your number')");
	System.out.println("'Bingo'(if 'number' = 'your number')");
	System.out.println("'Low'(if 'your number'/2 < 'number' < 'your number')");
	System.out.println("'High'(if 'number' < your number)");
	BufferedReader d=new BufferedReader(new InputStreamReader(System.in));
	int Num = 3636;
	String Ans="";
	int count=1;
	double min=1.0;
	double max=10000.0;
	boolean flag = true;
	while(true){
	    if(max-min==1)flag=false;
	    System.out.print("Your Number is "+Num+" ?"+" ->");
	    Ans = d.readLine();
	    if(check(Ans,"b","B"))break;
	    if(check(Ans,"v","V")){
		if(check(Ans,"h","H")){
		    System.out.print("Very High? ");
		    if(flag){
			max=(int)((2.0/11.0)*max);
			Num=(int)((7.0*min+4.0*max)/11.0+0.5);
			count++;
			continue;
		    }else{
			max=(int)(((double)Num)/2.0);
			Num=(int)((max+min)/2.0+0.5);
			count++;
			continue;
		    }
		}else{
		    if(check(Ans,"l","L")){
			System.out.print("Very Low? ");
			if(flag){
			    min=(int)(max*8.0/11.0);
			    Num=(int)((min+max)/2.0+0.5);
			    flag=false;
			    count++;
			    continue;
			}else{
			    min=Num*2+1;
			    Num=(int)((min+max)/2.0+0.5);
			    count++;
			    continue;
			}
		    }else{
			System.out.println("Prease obey me.");
		    }
		}
	    }else{
		if(check(Ans,"h","H")){
		    System.out.print("High? ");
		    if(max==Num)Num--;
		    if(min==Num)Num++;
		    if(flag){
			min=(int)(max*2.0/11+0.5);
			max=Num-1;
			Num=(int)((min+max)/2.0+0.5);
			flag=false;
			count++;
			continue;
		    }else{
			max=Num-1;
			Num=(int)((min+max)/2.0+0.5);
			count++;
			continue;
		    }
		}else{
		    if(check(Ans,"l","L")){
			System.out.print("Low? ");
		        if(min==Num)Num++;
			if(max==Num)Num--;
			if(flag){
			    min=Num+1;
			    max=(int)(max*8.0/11.0+0.5);
			    Num=(int)((min+max)/2.0+0.5);
			    flag=false;
			    count++;
			    continue;
			}else{
			    min=Num+1;
			    Num=(int)((min+max)/2.0+0.5);
			    count++;
			    continue;
			}
		    }else{
			System.out.println("Prease obey me.");
		    }
		}
	    }
	}
    }
}
最小ではないものの,1を含む区間で全体の4/11を質問に選ぶというのは,良い近似になっています.平均質問回数も11.2109回と最適値11.1625回に近い値を達成しています.