平均と標準偏差、偏差値を求める Java プログラム


何かのレポートで、平均値と偏差値を求める Java プログラムを書けとあったので。
要求されるレベルはべた書きで、配列を100個固定で用意しておけば OK とかですが、Java ならクラスとか使わないとだめでしょ。ということで。

#プログラム本体
```java:scoring.java
package Scoring;

import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author 123abc
 */
class Score {
    // 変数の宣言
    public static List<Person> person = new ArrayList<>();
    private static double mean;
    private static double std;

    // 計算済みのフラグ
    private static boolean mean_Calculated = false;
    private static boolean std_Calculated = false;

    // 点数, 標準偏差を格納するクラスを定義
    class Person {
        double score;  // 点数
        double Tscore; // 偏差値

        // 数値が一つだけ渡されたときは score として扱う
        Person(double i) {
            score = i;
            mean_Calculated ^= mean_Calculated;  // 平均値の再計算要
        }
    }

    // 点数を追加
    public void addScore(double i) {
        person.add(new Person(i));
    }

    // 平均を求めて返す
    public double mean() {
        if (mean_Calculated) return mean; // 計算済みの平均を返す
        mean = 0.0;
        for (Person P : person) {
            mean += P.score;
        }
        mean /= person.size();
        mean_Calculated = true; // 平均値は計算済み
        std_Calculated ^= std_Calculated; // 標準偏差の再計算要
        return mean;
    }

    // 標準偏差を求めて返す ※事前に平均が求められていること
    public double std() {
        if (!mean_Calculated) mean(); // 平均値を計算していなければ計算する
        if (std_Calculated) return std; // 計算済みの標準偏差を返す
        std = 0.0;
        for (Person P : person) {
            std += Math.pow((P.score - mean), 2.0);
        }
        std = Math.sqrt(std/person.size());
        std_Calculated = true; // 標準偏差は計算済み
        return std;
    }

    // 各人の偏差値を求める ※事前に平均と標準偏差が求められていること
    public void updateTscore() {
        if (!mean_Calculated) mean(); // 平均値を計算していなければ計算する
        if (!std_Calculated) std(); // 標準偏差を計算していなければ計算する
        for (Person P : person) {
            P.Tscore = (P.score - mean) / std * 10 + 50;
        }
    }
}

public class main {
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        java.util.Scanner scan = new java.util.Scanner(System.in);
        // インスタンスを作成
        Score sc = new Score();

        // 点数を入力、負の値が入力されたら終了
        do {
            double score;
            System.out.println("点数を入力してください.");
            score = scan.nextDouble();
            if (score < 0) break;
            sc.addScore(score);
        } while (true);

        // 平均点を求めて表示
        System.out.println("平均: " + sc.mean());

        // 標準偏差を求めて表示
        System.out.println("標準偏差: " + sc.std());

        // 各々の偏差値を求めて表示
        sc.updateTscore();
        System.out.println("点数 : 偏差値");
        sc.person.forEach(P -> System.out.printf("%f : %f%n", P.score, P.Tscore));
    }
}
```
#実行結果
そのまま NetBeans 上で走らせると
※入力は 10, 20, 30, 40, 50 の 5件にしてみました。

```sh:出力
run:
点数を入力してください.
10
点数を入力してください.
20
点数を入力してください.
30
点数を入力してください.
40
点数を入力してください.
50
点数を入力してください.
-1
平均: 30.0
標準偏差: 14.142135623730951
点数 : 偏差値
  10 : 35.857864
  20 : 42.928932
  30 : 50.000000
  40 : 57.071068
  50 : 64.142136
ビルド成功(合計時間: 5秒)
```
偏差値の出力部分は .forEach の使い方とか、書式付き出力を試したり。
printf("%f") にすると println() より桁数が縮むのはなんでかな？

.forEach と、拡張for の比較。
List person と、Class Person の名前を一緒にしたのが間違いだったなぁ。大文字小文字で見分けてください←ダメなやつ

```java:.forEach
sc.person.forEach(P -> System.out.printf("%4d : %f%n", (int) P.score, P.Tscore));
```

```java:拡張for
for (Score.Person P : sc.person) {
  System.out.printf("%4d : %f%n", (int) P.score, P.Tscore);
}
```
% 変数は小文字始まりしてるのに、Person の一時変数は P なのか。適当だなぁ。

レポートには余計な機能まで色々と(^^;
標準偏差を計算するのに必要な平均値、偏差値を求めるときに必要な平均値と標準偏差が事前に求めていない場合は関数の中で再帰的に処理されるようにフラグを用意 (mean_Calculated & std_Calculated)。そのフラグを用いて、すでに計算済みの場合は再計算せずに値を返すようにした。

負の数の判定は if(signum() == -1.0) の方が早くなるのかな？
A ^= A と、A = false はどっちが早いのかなぁ？誰か試してみてください。

[Javaコーディング規約 4.11 クラス変数](https://future-architect.github.io/coding-standards/documents/forJava/Java%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E8%A6%8F%E7%B4%84.html#%E3%82%AF%E3%83%A9%E3%82%B9%E5%A4%89%E6%95%B0) によると、クラス変数は sc.person と参照するより Score.person と参照したほうがよさそうですねぇ。VBS だと new したオブジェクトからしか参照できななかった気がするけど。
メソッドには sc.mean() としないとアクセスできないのに、クラス変数は Score.person.forEach でアクセスするのが推奨というのは対称性がないですねぇ。両方ともクラス変数を操作してるのに。

※12/13 17:00 フラグ判定のバグ修正
　std() メソッドの最初 2行の if 文、上下逆だったので修正。
std_Calculated を評価するときは、必ずその前に mean_Calculated を評価する必要がある。updateTscore() の if 文はすぐに気づいて直したのになぁ。