11/12 簡単なグラフィクス(1)


前回までの補足


10/22の課題について

簡単な電卓プログラムを作るというもので,多くの人が正しいプログラムを 提出してくれた.

解答例(1)

//
import java.io.*; // 入力に関するクラスを使う時は必要
class Kadai1022{
   // throws IOException で内部で入出力エラーが起きる可能性があることを示す
  public static void main(String[] args) throws
 IOException{
  // 入力をするためには,System.inからBufferedReaderを作らなくてはいけない
    BufferedReader d=new BufferedReader(new InputStreamReader(System.in));
    double sum=0.0,num=0.0;
    // for文の初期化,終了条件,繰り返し部分を省略して書くこともできる
    // この場合は, break によって明示的に抜けないと無限の繰り返し
    // になる
     sum=Double.parseDouble(d.readLine());
    System.out.println(sum);
    for(;;){
      // 1行読み込んで整数に変換する
      String s;
      s=d.readLine();
      //もし=を入力すると終了する
      if(s.equals("=")) break;
      num=Double.parseDouble(d.readLine());
     //足し算か引き算かかけ算かわり算をする。
      if(s.equals("+")){
	  sum=sum+num;
	  System.out.println(sum);}
      if(s.equals("-")){
	  sum=sum-num;
	  System.out.println(sum);}
      if(s.equals("*")){
	  sum=sum*num;
	  System.out.println(sum);}
      if(s.equals("/")){
	  sum=sum/num;
	  System.out.println(sum);}
    }
    
  }
}
講義の範囲内で書かれた素直なプログラムである.ただし,ifが4つ並んでいる 部分は,2つめ以降は else if にしたい.また,
	  System.out.println(sum);
は合流後に書いても良い.殆んどの人はこれと同じようなプログラムを書いて いた.回答のバリエーションとしては,
      if(s.equals("=")) break;
の代りに,
      if(s.charAt(0)=='=')break;
のようにして,文字列の比較の代りに文字列sの最初の文字を取ってから,文 字の比較をおこなうものがあった.このようにすると,if ,else if 文の代りに switch文が使えるというメリットがある.

他のバリエーションとしては,

      num=Double.parseDouble(d.readLine());
にあたるコードを
      if(s.equals("+")){
        // この部分
      }
に入れたものなどがあった.機能を拡張したりして工夫をしたプログラムも紹 介する.
解答例(2)
/**
 * Kadai1022
 *
 * XXXXXXX OOOO
 *
 * 計算機プログラミング
 * 演算子の入力と数字の入力を、Enterをはさんで交互に繰り返して計算させる。
 * 数字を入力した時点で現在の計算結果を表示させる。
 * 最初の入力は数字として、以降演算子(+,-,*,/)、数字の順に入力。
 * 演算子として=を入力した時点で計算を終了する。
 * わり算に関しては、0で除算できないようにした。
 *
 * 機能拡張をする際は、Operatorクラスを派生させて、新しい演算子を
 * 定義する。
 * そして、staticメンバのoperatorsに、演算子の文字列をキーとして、
 * hashに追加する。
 *
 * History
 * 2002.10.30 ver.0.01 新規作成
 */
import java.io.*;
import java.util.HashMap;

class Kadai1022 {
    /** 演算子のリスト(Hash) */
    private static HashMap operators = new HashMap();
    
    static {
	// 使用したい演算子を追加
	// hashのキーに演算子の文字列を
	operators.put("+", new Addition());
	operators.put("-", new Subtraction());
	operators.put("*", new Multiplication());
	operators.put("/", new Division());
    }

    /**
     * main関数
     */
    public static void main(String[] args) throws IOException {
	/** 現在計算中の数値 */
	double currentNumber = 0;

	// ストリームの準備
	BufferedReader in 
	    = new BufferedReader(new InputStreamReader(System.in));
	
	// 最初の数字の代入
	for (;;) {
	    try {
		currentNumber = Double.parseDouble(in.readLine());
		break;
	    } catch(IOException e) {
		throw e;
	    } catch(NumberFormatException e) {
		System.out.println("数字を入力してください。");
	    } 
	}
	
	// "="を入力されるまで、演算のくりかえし
	String input;
	while (!(input = in.readLine()).equals("=")) {
	    Operator operator = ((Operator)operators.get(input));
	    // nullが返るのはhashにないとき
	    if (operator == null) {
		System.out.println("その演算子はサポートしていません");
	    } else {
		currentNumber = operator.calculate(currentNumber, in);
		System.out.println("" + currentNumber);
	    }
	}
    }
}


/**
 * Operatorクラス
 *
 * 演算子の抽象クラス。1つの数値と入力ストリームから演算結果を返す。
 * MEMO:むしろインターフェースの方がいいかもしれない
 */
abstract class Operator {
    abstract double calculate(double arg, BufferedReader in);
}


/**
 * BiomialOperatorクラス
 *
 * 二項演算子の抽象クラス。
 */
abstract class BinomialOperator extends Operator {
    /**
     * 1回の入力で計算を行う
     * 計2つの引数を使って、それぞれの具象クラスの演算結果を返します。
     */
    double calculate(double arg, BufferedReader in) {
	for (;;) {
	    /** 第2引数 */
	    double arg2;
	    for (;;) {
		try {
		    arg2 = Double.parseDouble(in.readLine());
		    break;
		} catch(Exception e) {
		    System.out.println("数値を入力してください。");
		}
	    }

	    /** 計算結果 */
	    double answer;
	    try {
		answer = calculate(arg, arg2);
		return answer;
	    } catch(Exception e) {
		System.out.println(e);
	    }
	    
	}
    }

    /**
     * 二項演算をします。
     * 具象クラスでそれぞれの演算を実装します。
     * エラーはExceptionで指示。
     * TODO: もっとうまいエラーの指定方法は?
     */
    abstract double calculate(double arg, double arg2) throws Exception;
}

/**
 * Additionクラス
 * 足し算する。
 */
class Addition extends BinomialOperator {
    double calculate(double arg, double arg2) throws Exception {
	return arg + arg2;
    }
}

/**
 * Subtractionクラス
 * 引き算する。
 */
class Subtraction extends BinomialOperator {
    double calculate(double arg, double arg2) throws Exception {
	return arg - arg2;
    }
}

/**
 * Multiplicationクラス
 * かけ算する。
 */
class Multiplication extends BinomialOperator {
    double calculate(double arg, double arg2) throws Exception {
	return arg * arg2;
    }
}

/**
 * Divisionクラス
 * わり算する。
 */
class Division extends BinomialOperator {
    /**
     * わり算の結果を返します。
     * 0での除算はExceptionとします。
     */
    double calculate(double arg, double arg2) throws Exception {
	if (arg2 == 0)
	    throw new Exception("0では除算できません。");
	return arg / arg2;
    }
}


解答例(3)
//
//
import java.io.*;
public class Kadai1022{
  public static void main(String argv[]) {
    BufferedReader keyin=new BufferedReader(new InputStreamReader(System.in));
    double ans = 0.0;
    String sign = "+";
    try {
      do {
	double num = new Double(keyin.readLine()).doubleValue();
        if (sign.equals("+"))
	    ans += num;
	else if (sign.equals("-"))
	    ans -= num;
	else if (sign.equals("*"))
	    ans *= num;
	else if (sign.equals("/")) {
	    if (num == 0.0)
		throw new ArithmeticException();
	    ans /= num;
	}
        System.out.println(ans);
        sign = keyin.readLine();
      }while (sign.equals("+")||sign.equals("-")||sign.equals("*")||sign.equals("/"));
    } catch (IOException ie) {
	System.out.println(ie);
    } catch (NumberFormatException ne) {
	System.out.println(ne);
	System.out.println("数値型に変換できません");
    } catch (ArithmeticException ae) {
	System.out.println(ae);
	System.out.println("0の除算エラー");
    }
  }
}
解答例(2)は四則演算子以外の演算子を導入したい時に,容易に追加できるよ うに工夫したプログラムだが,一部この講義の範囲を越えている.

解答例(3)はエラー処理を徹底しておこなったプログラムである.


誤答例
// 学生証番号 XXXXX
// 氏名 XXXXX
// プログラムの内容;演算子と数値を交互に入力することにより四則演算を行う。


import java.io.*;

class Kadai1022 {
    public static void main(String[] args) throws IOException{
	BufferedReader d = new BufferedReader(new InputStreamReader(System.in));
	
	double sum=0.0,num;
	String read;

	for(;;){
	    read = d.readLine();
	    if(read.equals("=")) break;
	   
	    if(read.equals("+")){
		num=Double.parseDouble(d.readLine());
		sum=sum+num;
	    }
	    if(read.equals("-")){
		num=Double.parseDouble(d.readLine());
		sum=sum-num;
	    }
	    if(read.equals("*")){
		num=Double.parseDouble(d.readLine());
		sum=sum*num;
	    }
	    if(read.equals("/")){
		num=Double.parseDouble(d.readLine());
		sum=sum/num;
	    }
	   
	    System.out.println("sum = "+sum);
	}
	
	System.out.println("sum = "+sum); 
    }
}
よくあった誤答例である.電卓は一番最初に数字を入力する必要があるが, まず演算子の入力を求めている.
//XXXXXX
//本プログラムでは、最初の入力が数字、以降、演算子、数字、演算子、数字...と入力されると想定しています。
//電卓を終了するときは=を演算子を入力する部分で入力してください。

import java.io.*;
class Kadai1022{
  public static void main(String [] args) throws IOException{
    BufferedReader d=new BufferedReader(new InputStreamReader(System.in));
    double num1=Double.parseDouble(d.readLine());
    System.out.println(num1);
    for(;;){
      String ope=d.readLine();
      if(ope.equals("=")) break;
      double num2=Double.parseDouble(d.readLine());
      double ans=num1;
      if(ope.equals("+")) ans=ans+num2;
      else if(ope.equals("-")) ans=ans-num2;
      else if(ope.equals("*")) ans=ans*num2;
      else if(ope.equals("/")) ans=ans/num2;
      System.out.println(ans);
    }
  }
}
forループの中でnum1は一度も代入されないので,ansにはnum1とnum2を最後の 演算子で計算した値になる.これは,電卓の動作及び入出力例と合わない.

前回の課題について

乱数からMooNumberを作る部分が難しかったかも知れないので,解説する.
  MooNumber(){
    int tmpDigits[]=new int[10];
    int i;
   // tmpDigitsに 0123456789を入れる
    for(i=0;i< 10;i++){
      tmpDigits[i]=i;
    }
    // 乱数を生成するためのクラス
    Random r=new Random();
    // 0 と 0-9(の範囲の乱数を添字とする tmpDigitsの要素)を, 
    // 1と1-9を, 2と2-9を,3と3-9を入れ換える
    for(i=0;i< 4;i++){
      int j=i+r.nextInt(10-i);
      digits[i]=tmpDigits[j];
      tmpDigits[j]=tmpDigits[i];
    }
  }
の,
    for(i=0;i< 4;i++){
      int j=i+r.nextInt(10-i);
      digits[i]=tmpDigits[j];
      tmpDigits[j]=tmpDigits[i];
    }
の部分は,自然に書くと以下のようになる.
    // tmpDigitsを乱数で並び替え
    for(i=0;i< 10;i++){
      int j=i+r.nextInt(10-i);
      // tmpDigits[i]とtmpDigits[j]を交換
      int tmpVal=tmpDigits[j];
      tmpDigits[j]=tmpDigits[i];
      tmpDigits[i]=tmpVal;
    }
    // tmpDigitsの最初4つをdigitsにコピー
    for(i=0;i< 4;i++)
      digits[i]=tmpDigits[i];
しかし,最初のfor文でiを0から9まで動かしているが,jは常にi以上なので, iが4以上の時の並び替えをやっても,digitsには関係しない.するとこの2つ のループは一緒にすることができる.
    // tmpDigitsを乱数で並び替え
    for(i=0;i< 4;i++){
      int j=i+r.nextInt(10-i);
      // tmpDigits[i]とtmpDigits[j]を交換
      int tmpVal=tmpDigits[j];
      tmpDigits[j]=tmpDigits[i];
      tmpDigits[i]=tmpVal;
      digits[i]=tmpDigits[i];
    }
ここで,ループ中のtmpDigits[i]は後で参照されない領域だということがわ かる.そこで,tmpDigits[i]への代入を省略すると,
    // tmpDigitsを乱数で並び替え
    for(i=0;i< 4;i++){
      int j=i+r.nextInt(10-i);
      // tmpDigits[i]とtmpDigits[j]を交換
      digits[i]=tmpDigits[j];
      tmpDigits[j]=tmpDigits[i];
    }
となるわけである.
次へ進む