SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

「成分解析」を行うプログラムを例にして覚える乱数の使い方

標準正規分布に基づく乱数の簡易作成

データの作成

 出力の元になるデータは、プログラムに直接書かないで、別ファイルで管理しましょう。[プロジェクト]メニューの[新しい項目の追加]で[テキストファイル]を選び、ファイル名を「list.txt」にして[追加]ボタンを押してください。追加するときは、ソリューションエクスプローラで[CZ成分解析]を選択しておいてください。「list.txt」の中身は、例えば次のようにしてください(プサイさんのサイトから頂きました)。

下心
微妙さ
優雅さ
華麗さ
かわいさ
やさしさ
やましさ
やらしさ
むなしさ
ツンデレ
厳しさ
世の無常さ
ハッタリ
ビタミン
努力
気合
根性
砂糖
食塩
愛
電波
毒電波
元気玉
怨念
大阪のおいしい水
明太子
勇気
運
電力
小麦粉
汗と涙(化合物)
覚悟
大人の都合
見栄
欲望
嘘
真空
呪詛
信念
夢
記憶
鉄の意志
カルシウム
魔法
希望
不思議
勢い
度胸
乙女心
罠
花崗岩
宇宙の意思
犠牲
毒物
鉛
海水
蛇の抜け殻
波動
純金
情報
知識
知恵
魂の炎
媚び
保存料
着色料
税金
歌
苦労
柳の樹皮
睡眠薬
スライム
アルコール
時間
果物
玉露
利益
赤い何か
白い何か
鍛錬
月の光
回路
野望
陰謀
雪の結晶
株
黒インク
白インク
カテキン
祝福
気の迷い
マイナスイオン
濃硫酸
ミスリル
お菓子
言葉
心の壁
成功の鍵
理論
血

 「list.txt」を選んで、プロパティのビルドアクションを[埋め込まれたリソース]に変えてください。こうすることにより、このデータが実行ファイルに埋め込まれ、「list.txt」がなくても実行できるようになります。

イベントハンドラの作成

 以下の機能を実装します。

  • MainFormのLoadイベント(初期表示時の処理)
  • btnAnalyzeのClickイベント([解析]ボタンを押したときの処理)

usingの指定

 「MainForm.cs」の最初に、以下を追加してください。「MainForm.cs」を開くには、ソリューションエクスプローラで「MainForm.cs」を右クリックして、[コードの表示]を選んでください。

MainForm.cs
using System.IO;

MainFormのLoadイベント

 成分のリストは、初期表示時に作成します。初期表示時は、MainFormのLoadイベントが呼ばれます。MainFormのデザイン画面でMainFormをダブルクリックしてください(コントロールをダブルクリックしないようにしてください)。MainForm_Loadを次のように修正します。

MainForm_Load
private string[] list;
private void MainForm_Load(object sender, EventArgs e)
{
    try
    {
        using (StreamReader sr = new StreamReader(GetType().Assembly.
            GetManifestResourceStream("CZ成分解析.list.txt"),
            Encoding.Default))
        {
            list = sr.ReadToEnd().Split('\n');
        }
    }
    catch (FileNotFoundException)
    {
        list = new string[] { "???" };
    }
}

 メンバ変数listは、成分の全文字列が入ります。

btnAnalyzeのClickイベント

 MainFormのデザイン画面でbtnAnalyzeをダブルクリックしてください。btnAnalyze_Clickを次のように修正します。

btnAnalyze_Click
private void btnAnalyze_Click(object sender, EventArgs e)
{
    if (string.IsNullOrEmpty(tbInput.Text))
    {
        MessageBox.Show("成分を入力してください");
        return;
    }
    int seed = tbInput.Text.GetHashCode(); // 乱数の元
    // 毎日結果が変わるように
    seed ^= DateTime.Now.DayOfYear;
    Random rand = new Random(seed);        // 乱数
    int[] index = new int[list.Length];    // ランダムな順番
    for (int i = 0; i < index.Length; ++i) index[i] = i;
    Algo.RandomShuffle(rand, index);       // ランダムシャッフル
    // 結果用
    List<Pair<int, double>> res = new List<Pair<int, double>>();
    int n = Math.Max(1, Math.Min(index.Length,
        (int)(4 + NormalRandom(rand))));
    for (int pos = 0; pos < n; ++pos)
    {
        double d = 1.0 / (pos + 1) * Math.Exp(3 + NormalRandom(rand));
        res.Add(new Pair<int,double>(index[pos], d));
    }
    res.Sort(delegate(Pair<int, double> pr1, Pair<int, double> pr2)
        { return -pr1.Second.CompareTo(pr2.Second); });
    double dsum = 0;
    foreach (Pair<int, double> pr in res) dsum += pr.Second;
    StringBuilder sb =
        new StringBuilder(tbInput.Text + "の成分解析結果 :\r\n");
    foreach (Pair<int, double> pr in res)
        sb.Append(string.Format("{0}の{1}%は{2}で出来ています。\r\n",
            tbInput.Text, (int)(pr.Second * 100 / dsum),
            list[pr.First]));
    tbResult.Text = sb.ToString();
}
private double NormalRandom(Random rand)
{
    return rand.NextDouble() + rand.NextDouble() + rand.NextDouble()
        + rand.NextDouble() + rand.NextDouble() + rand.NextDouble()
        + rand.NextDouble() + rand.NextDouble() + rand.NextDouble()
        + rand.NextDouble() + rand.NextDouble() + rand.NextDouble()
        - 6;
}

 成分解析のメインの処理になります。

  1. 入力文字列が空でないかをチェックします。
  1. 入力文字列からGetHashCodeを使って、シードを計算します。
  2. シードは、次の条件を満たす必要があります。
    • 入力文字列が同じなら同じ値となること
    • 入力文字列が異なれば、なるべく違う値となること
    この条件を満たすものに「ハッシュコード」があります。ここでは、このハッシュコードを利用します。
  1. シードから乱数を作成します。同じシードを与えると、乱数は同じように発生します。
  1. 乱数を利用して、添え字の配列をランダムシャッフルします。この添え字の順番に、成分が表示されます。
  2. ある集合から重なりなくランダムなデータを取得する場合、ランダムシャッフルを行うと良いです。例えば、1からnまでの乱数を発生させて、重なりがあったら別の乱数を発生させる方法は、効率が悪くなります。
  1. 成分の数を決めます。平均4個、標準偏差1個の正規分布に従う乱数を切り捨てて使いましょう。正規分布は、-∞から∞の値となる可能性があるため、1からリスト数の間に入るように調整します。
  1. 正規分布に従う乱数は、通常はボックスミューラー法などを利用して発生させます。ここでは、単純に12個の一様乱数を足して6を引いて発生させます。この方法は、簡単なプログラムで(ほぼ正しい)正規分布を発生させることができます。
  1. 各成分ごとに割合を決定します。割合の平均がジップの法則に従うにように1.0/(pos+1)を基準にしましょう。また、ランダムになるように乱数をかけます。正規分布を用いると負の値になる可能性があるため、ここでは負の値をとらない対数正規分布を用います。対数正規分布は正規分布の指数関数をとることで得られます。
  1. 一般的な分布の乱数は、元になる分布の確率密度関数の累積関数の逆関数に対して、0~1の一様乱数を適用することで得られます。
  1. 発生させた割合の大きい順にソートします。C# 2.0ではAnonymous Methodを用いることで、複雑なソートも簡易に記述できるようになっています。
  1. 発生した割合からパーセンテージを計算できるように、割合の総和を求めます。
  1. 成分を計算し、結果を表示します。

実行

  1. [ビルド]メニューの[ソリューションのビルド]を行います
  2. コンパイルでエラーが出るときは、ヘルパークラスの名前空間が合っているかどうか確認してみましょう。
  3. [デバッグ]メニューの[デバッグなしで開始]を選んで実行します
  4. 文字を入力して[Enter]キーを押してください
  5. フォームのAcceptButtonであるbtnAnalyzeのハンドラが呼ばれ、結果が表示されます。

まとめ

 以下の演習を行いました。

  • 2つの値を扱うPair構造体の使用
  • ランダムな順列の生成方法
  • 文字列から特定の整数値を作成する方法
  • 簡単な標準正規分布の乱数と、対数正規分布の乱数の作成方法

参考サイト

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
特集記事連載記事一覧

もっと読む

この記事の著者

斉藤 努(サイトウ ツトム)

C#プログラマ

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/375 2006/05/11 12:05

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー