;

スポンサーサイト

0

    一定期間更新がないため広告を表示しています

    • 2019.10.15 Tuesday
    • -
    • -
    • -
    • by スポンサードリンク

    モンティホール問題をCプログラムで解く

    0
      モンティ・ホール問題
      解答者の目の前にある3つの扉。このうち1つの扉に隠された豪華景品を当てることができるか、というもの。
      単純に選んで終わりという訳ではなく、少し変わった手順で行われる。

      1) 解答者は、3つの扉の中から、正解だと思う扉を選ぶ
      2) 司会者は、解答者が選んだ以外の2つの扉のうち、はずれの扉を一つオープンする
      3) 解答者は、残る2つの扉から、再度正解だと思う扉を選ぶ
       → つまり、1) の解答から変えるか、変えないかのどちらか

      一見、3) で選択を変えても変えなくても、当たる確率は同じ(1/2) に見える。
      しかし実際には、最後の選択によって当たる確率が全く異なってしまうという、不思議な問題。

      もちろん理論値は、数学的に明らかにされているのだが、やっぱり感覚的には納得しづらい。
      確率を追ってみてもよいけれど、こうした問題はシミュレーションをしてみるのが一番だ。
       

      /* MontyHall.c  :  モンティホール問題をシミュレーションする */

      #include <stdio.h>
      #include <stdlib.h>             /* random 関数 */

      #define NO_CHANGE  (0)
      #define YES_CHANGE (1)
      #define GET_RANDOM_ONE_TO_THREE ((rand() % 3) + 1)  /* 1 から 3 の乱数を取得 */
      #define LOOP_TIMES (1000000)                           /* 繰り返し数 */

      /* 解答者の答えを除く2つから、不正解のものを開ける
       * 戻り値:オープンする箱の番号 */
      static int OpenMonty(int correct, int answer_first) {
          int opened;
          int i;
          int foolTable[2];

          if (((correct < 1) && (correct > 3)) ||
              ((answer_first < 1) && (answer_first > 3))) {
              return -1;
          }
          
          if (correct == answer_first) {
              /* 解答が正解→のこり2つのうちどちらかをランダムで開く */

              /* テーブル生成 */
              switch (correct) {
                  case (1):
                      foolTable[0] = 2;
                      foolTable[1] = 3;
                      break;
                  case (2):
                      foolTable[0] = 1;
                      foolTable[1] = 3;
                      break;
                  case (3):
                      foolTable[0] = 1;
                      foolTable[1] = 2;
                      break;
                  default:
                      /* ありえない */
                      break;
              }
              opened = foolTable[(rand() % 2)];
          }
          else {
              /* 解答が不正解→解答でも正解でないものを開く */
              for (i = 1; i <= 3; i++) {
                  if ((i != correct) && (i != answer_first)) {
                      opened = i;
                      break;
                  }
              }
          }
          
          return opened;
      }

      /* モンティのオープン結果とポリシーをもとに最終的な答えを決める */
      static int GetAnswerSecond(int answer_first, int open, int policy)
      {
          int ans_second, i;
          
          if (policy == NO_CHANGE) {
              /* 1回目と解答を変えない */
              ans_second = answer_first;
          }
          else {
              for (i = 1; i <= 3; i++) {
                  /* 解答を変える */
                  if ((i != open) && (i != answer_first)) {
                      ans_second = i;
                      break;
                  }
              }
          }
          return ans_second;
      }

      /* モンティホールをシミュレーションする
       * 引数: 解答を変えない(NO_CHANGE), 変える(YES_CHANGE) のいずれか
       * 戻り値:正解なら1, 不正解なら0
       */
      static int SimulateMontyHall(int policy)
      {
          int i;

          int answer_first, answer_second, correct, open;

          if ((policy != NO_CHANGE) && (policy != YES_CHANGE)) {
              return -1;
          }
          
          /* モンティホールの正解をランダムで取得 */
          correct = GET_RANDOM_ONE_TO_THREE; /* 1から3 */
              
          /* 回答者の答え(一段階目)をランダムで指定 */
          answer_first = GET_RANDOM_ONE_TO_THREE;  /* 1から3 */

          /* モンティは残りの2つのうち、はずれを一つオープンする */
          open = OpenMonty(correct, answer_first);
              
          /* 回答者はポリシーに基づき、答えを変更する  */
          answer_second = GetAnswerSecond(answer_first, open, policy);

          /* 答え合わせ */
          if (answer_second == correct) {
              return 1;               /* 正解 */
          }
          else {
              return 0;               /* 不正解 */
          }
      }

      int main(void) {

          int ret, i;

          printf("1) 選択肢を変えない人¥n");
          ret = 0;
          for (i = 0; i < LOOP_TIMES; i++) {
              ret += SimulateMontyHall(NO_CHANGE);
          }
          printf(" >> 正解率 %f (%d / %d)¥n", ((double)ret / (double)LOOP_TIMES),
                 ret, LOOP_TIMES);
          
          printf("2) 選択肢を変える人¥n");
          ret = 0;
          for (i = 0; i < LOOP_TIMES; i++) {
              ret += SimulateMontyHall(YES_CHANGE);
          }
          printf(" >> 正解率 %f (%d / %d) ¥n", ((double)ret / (double)LOOP_TIMES),
                 ret, LOOP_TIMES);
          return 0;
      }

      ポイントは
      ・3) で解答者が選択を変えるか変えないかは policy として与えている
      ・2) で司会者がオープンする扉は、1) の解答が正解か否かによってアルゴリズムが変化する
      ・ループ回数は LOOP_TIMES で定義

      結果
       回数を増やして行きながらシミュレーションする。

      3) で選択肢を変えない派の人は、正答率が 0.333... に近似し、
      3) で選択肢を変える派の人は、正答率が 0.666... に近似していくことが分かる。
      → これは、それぞれの理論値(1/3, 2/3) に相当する。

      選択肢を変えると、当たる確率は2倍に膨れ上がる。
      自分の意思を貫くのも良いけれど、たまには人の意見も聞き入れたほうがお得だよ、ということを
      この問題は示唆しているのかもしれない。


      <参考>
       


      円と私と円周率。

      0
        謎のタイトルは無視して頂いて、突然ですが円周率の話です。

        円周率とは、円の直径に対する、円周の比のことです。
        私が小学生の頃は、3.14 として学びました。今もそうだと思います。

        ゆとり教育が話題になったころ、この円周率を3にする、なんていう話題がありましたね。

        「計算力が落ちるじゃないか」
        「いやいや 3.14 だって厳密な値じゃないし」
        「もう最初からπでいいじゃん」
        とか、色々議論はできそうですよね。

        このトピック、円周率 3が何を意味するのか、という視点から見ていくと非常に面白いのです。



        円です。直径を R とすると、円周率の定義から、円周の長さは、


        円周率が3と仮定するなら、こうなります。
        … (*)

        次にこの円に内接する、正六角形を考えてみます。


        正六角形の対角線を結ぶと、6つの正三角形に分けることができます。
        この正三角形の一辺の長さは、 R/2 ですよね。

        ということは、正六角形の周の長さは、

        あれれ、(*)と同じになりましたね。

        つまり、円周率を3の世界では、
        円の周の長さを求めたつもりが、実は正六角形の周りの長さを求めたことにしかならない
        ということになるのです。

        こう考えると、3と言うのが如何に乱暴な考え方かどうかが見えてきます。

        ところで、以前会社の飲み会で、「○○さんはもしかしてゆとり世代?円周率 3 で覚えた人?」なんてことを聞かれました。
        若干カチンと来た私、「違いますよ〜。円を正六角形とみなす教育なんて絶対許せません。」 と言ってやりました。
        先輩はポカーン。理系なのに通じなかったのが残念です。

        補足)
        同じように、円の面積の公式を、円周率3で考えてみると、今度は正十二角形の面積と一致します。
        (三角形一個分の面積を求めるのは少しだけレベルが上がります。ただし、三平方の定理だけでも導けます。)
        正六角形に比べると円に近いのでまだ許せる感じはしますが、やはり円とは言えないですね(汗)
         

        数学とアート

        0
          数学とアートは実は密接なつながりがあります。
          人が目で見て美しいと感じるとされる「黄金比」が最たる例ではないでしょうか。

          今回は、とある方程式から導かれる、不可思議で美しい軌跡の話です。
          「ローレンツモデル」と呼ばれる、次のような微分方程式です。



          理系でないと dx/dt とかピンと来ないかもしれませんが、x, y, z の3つの変数を持つ、タダの方程式です。

          # 微分の概念が入ってくるので、自力での計算はちょっと大変で、近似を使わないといけないのですが、
          # 本質ではないので省略します。

          この方程式をガリガリと解いていくと、時間軸 t に対して、x, y, z のそれぞれの値が得られます。
          この x, y, z を、3次元のグラフとしてプロットすると....



          蝶が羽を広げたように、2つのループが重なった綺麗な軌跡を描きます。
          味気ない方程式が、綺麗なアートを描く。何とも美しくないでしょうか。

          もっと掘り下げていくと、初期値をちょっと変えるだけで軌道が大きく変わったり、
          同じ軌道を通ることが無い=周期性がない、など、カオス理論の面白い性質が見えてきます。

          アートからアプローチすることで、数学の意外な一面が見えてくるのではないでしょうか。
          中学や高校で、こんな世界もあるんだよ、と教えて欲しいなと個人的には思います。
           

          | 1/1PAGES |

          PR

          calendar

          S M T W T F S
             1234
          567891011
          12131415161718
          19202122232425
          262728293031 
          << July 2020 >>

          counter

          ブログパーツUL5

          books

          ひろの最近読んだ本

          selected entries

          categories

          archives

          recent comment

          links

          profile

          search this site.

          others

          mobile

          qrcode

          powered

          無料ブログ作成サービス JUGEM