解答例 - j1.lesson04.TrapezoidRule

package j1.lesson04;

import java.io.*;

/**
 * 課題0403 - 解答例.
 @author s.arakawa
 @version $Id: TrapezoidRule_java.rps,v 1.1 2006/03/06 12:56:15 java2005 Exp $
 */
public class TrapezoidRule {

    /**
     * コンソールに積分区間(下端、上端の順)を入力させ、
     * sin(x)を指定された積分区間でxについて定積分の近似を求めるプログラム。
     @param args 無視される
     @throws IOException 入力中に例外が発生した場合
     */
    public static void main(String[] argsthrows IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        
        // 数式のマッピング
        // 単純な数式をプログラム上で表す場合、ある程度機械的に変換できるものもある
        // 出題者がいつもやっている方法を、簡単にしたものを紹介する
        //
        // 1. 記号をできるだけ数式とそろえる
        //    -> 記号はプログラム上で 変数 となり、名前を自由につけられる
        // 2. 長い数式は分解して分かりやすい名前をつける
        //    -> 変数へ数式を代入することにより、数式に名前をつけることができる
        // 3. 機械的に変換する
        
        // ヒントページの数式を参考に、プログラムを作成する
        
        //---------------------------------------
        // 1. 記号をできるだけ数式とそろえる
        //
        // プログラムの作法としてはあまりよくないが、使用する記号を先に用意する

        // x0: 開始位置
        System.out.print("開始位置を入力:");
        double x0 = Double.parseDouble(reader.readLine());
        
        // xn: 終了位置
        System.out.print("終了位置を入力:");
        double xn = Double.parseDouble(reader.readLine());

        // n: 分割数
        int n = 10000;
        
        // h: 一つ一つの台形の高さ
        double h = (xn - x0/ n;
        
        // xi は i の値によって変化するため、ここでは定義しない
        
        //---------------------------------------
        // 2. 長い数式は分解して分かりやすい名前をつける
        //
        // 今回使用する数式には 2 つのΣが存在している
        // それぞれに名前をつける
        //
        // 式を噛み砕いて考えると、
        // h / 2 * (上底の総和 + 下底の総和) であることが分かる
        // ※ 台形の左側を上底とした。気に入らなければ入れ替えてかまわない
        
        // 左側の Σ - 各台形の上底の総和
        // 変数を宣言して、それを加算(Σは総和なので加算)の単位元 0 で初期化する
        // upper: 上底の総和を保存する
        double upper = 0.0;
        
        // 左側の Σ - 各台形の下底の総和
        // 変数を宣言して、それを加算(Σは総和なので加算)の単位元 0 で初期化する
        // lower: 下底の総和を保存する
        double lower = 0.0;
        
        
        //---------------------------------------
        // 3. 機械的に変換する
        // 
        // 慣れるまでは難しいが、今回はΣくらいなので簡単に済ませる
        
        // 左側のΣは、次のように展開できる
        // まず、繰り返す条件を for で表現しなおす
        for (int i = 0; i <= n - 1; i++) {

            // Σの中で使用されている記号をプログラムで表現 (1)
            double xi = x0 + (xn - x0* i / n;
            
            // f(xi) で、ここで使用する関数は sin なので、Math.sinを使用する
            // それに対して fxi という名前をつけておく(2)
            double fxi = Math.sin(xi);
            
            // Σは上底の総和を表すので、上底の総和を保存している変数に足し合わせればよい
            upper += fxi;
        }
        
        // 右側のΣも同様
        // 繰り返す条件が異なるので注意
        for (int i = 1; i <= n; i++) {
            double xi = x0 + (xn - x0* i / n;
            double fxi = Math.sin(xi);
            // 右側のΣは下底の総和
            lower += fxi;
        }
        
        // h / 2 * (上底の総和 + 下底の総和) であるので、それを計算
        double result = h / (upper + lower);

        // 数式の変換が終わったので、最後に出力
        System.out.println("結果は" + result);
        
        // この方法ならばとりあえず実装が可能となる
        // しかし、上記の方法では無駄な処理が多いため、プログラムの性能が低下してしまう
        // - いくつかのsinの計算が余計 (Xi の下底 = Xi+1 の上底であるので、sinの計算を半分近くに減らせる)
        // - いくつかの変数が早すぎる時期に宣言されている
        //   -> これは計算機の資源を浪費することになるので、通常は使用する直前まで宣言しない
        //
        // 可読性と性能のトレードオフであるため、一概にどちらが良いとは言えない
        
        // このように、「最終的に何を行いたいのか」ということを紙の上にまとめて、
        // それを一つ一つ噛み砕いてプログラムを作成していくと間違いが少なくなる
        // 上記のような噛み砕き方は別に新しい手法でもなんでもなく
        // 課題0301 - ヘロンの公式 などで既に実践しているはずである
        // この問題は少しだけ規模が大きいため、混乱したかもしれない
    }
}