きっかけなんて些細なモンですよ。

NEW GAME!を観ました。プログラミング始めます。(20)

64のスマブラキャプテンファルコンが好きです。
f:id:kirintt:20171031001047p:plain


前回
nagoyanofes.hatenadiary.jp



※基本的に、愛知大学の有澤先生がプログラミングの講義で使用しているテキスト「Pythonによるプログラミング入門」に沿って進めています。
Python 2.7.13を使用しています。

今回のテーマは「誕生日の怪」です。

誕生日の怪

問題提起

この節では、最初に次のような問題が出された。

フランク先生はとても優しい先生で子供たちから慕われていた。ある日教室で「私の誕生日のパーティには必ず来てね」と生徒たちにせがまれ約束す事になった。フランク先生は約束した後でちょっと不安になった。誕生日が同じ子がいたら困るな.... 同じ日に 2 カ所には行けないよ.... でもフランク先生はこの不安を打ち消した。なあにクラスの子は 30 人だ。1年は 365 日もあるのだ。ぶつかるなんてよほど運が悪くないと...
(引用元:Pythonによるプログラミング入門, p. 47)

さて、どうしたもんかね。

コンピュータシミュレーション

テキストでは、とりあえず1~365の数字をランダムに30回発生させて、それらが被るかを見ることから始めている。

乱数の発生

いきなりだけどサンプルコード貼る。

譜 4.9 乱数を使ったシミュレーション

from random import *
for i in range(0,30):
    x=int(365*random())+1
    print x

(引用元:同上, p. 48)

このプログラムについて。ちなみにそれぞれ頭の数字は行数のこと。

  • 1:乱数(ランダムな数字)を使うための宣言。
  • 3:random() は0以上1未満の範囲から、ランダムに1つ数字を取り出すことができる。これを365倍すると、範囲が0以上365未満に。さらに、これを int() で囲むと、ランダムに選ばれた数字の小数点以下が切り捨てられ、整数になる。最後に1を足すと、ここで得られる x は1~365の整数のどれかになるって寸法。すっごーい!


結果

199
136
80
62
195
94
145
130
275
178
64
246
165
168
84
220
306
43
324
218
103
257
297
317
240
276
45
280
358
110

計算はランダムなので、これはあくまで一例。

整列すれば見やすくなる

さっきのプログラムの結果を見れば、数字が被っているかどうかは確かめられる。でも正直面倒くさい。
なんで面倒なのか? 表示される数字が小さい順とかになってないから?
じゃあ小さい順に結果を表示させればいいじゃん。
というわけで、次のサンプルプログラムがこちら。

譜 4.10 整列による譜 4.9 の改良版

from random import *
d=[]
for i in range(0,30):
    x=int(365*random())+1
    d.append(x)
print d
d.sort()
print d

(引用元:Pythonによるプログラミング入門, p. 48)

  • 2:[] は空のリスト。要素がいくつとかも特に決まってない状態。
  • 5:4行目で計算した乱数 x をリスト d の最後に追加。for を繰り返して x がどんどん追加されていく。
  • 7:for が終わって、リスト d に30個の乱数が詰まった状態で、そいつらを小さい順に並べ替え。


結果

[7, 262, 232, 95, 126, 334, 100, 205, 228, 153, 51, 265, 310, 247, 273, 244, 249, 352, 27, 57, 118, 148, 362, 287, 54, 56, 313, 191, 266, 274]
[7, 27, 51, 54, 56, 57, 95, 100, 118, 126, 148, 153, 191, 205, 228, 232, 244, 247, 249, 262, 265, 266, 273, 274, 287, 310, 313, 334, 352, 362]
  • プログラム見ればわかるけど、一つ目は並べ替えなし、二つ目は並べ替えあり。
  • これも乱数なので結果は毎回違う。
  • リストを表示すると、[1, 3, ...] みたいに表示される。
重複した誕生日だけを書き出す

並べ替えのおかげで多少見やすくはなったけど、どうせなら数字が被ってるかどうかも教えてくれませんかね…?

Python「仕方ねぇな」

譜 4.11 重なりだけを出力する譜 4.10 の更なる改良版

from random import *
d=[]
for i in range(0,30):
    x=int(365*random())+1
    d.append(x)
print d
d.sort()
print d
for i in range(0,29):
    if d[i]==d[i+1]: print d[i]

(引用元:Pythonによるプログラミング入門, p. 49)

  • 結果を並べ替えるプログラムの尻に2行だけ追加された。
  • 9, 10:リスト d の i 番目の数がそのすぐ次( i+1 番目)と同じなら、その数を表示。


成歩堂龍一!!

問1

最後におまけみたいな問題もあったのでご紹介。

 そこでフランク先生は誕生日の衝突の確率を理論的に見積もる事にしました。30 人の問題をいきなり扱うのは難しすぎるので、まずは 3 人から...
 3 人で誕生日の衝突が発生しない確率 P は
P = (365 / 365) * (364 / 365) * (363 / 365)
で計算できます。これぐらいなら電卓でも計算できますが、30 人だとやっぱり計算機ですね。やってみましょう。また P が 5 割を切るのは何人でしょうね。
(引用元:Pythonによるプログラミング入門, p. 49)

プログラム

from math import *
x = 1
d = []
n = 30 # クラス人数
for i in range(0, n):
    x = x * (365.0 - i) / 365.0
    d.append(x)
print d # 確認用
print # 改行
print "30 students:", x
for i in  range(0, n):
    if d[i] <= 0.5 :
        print "When", i + 1, "students, P is lower than 0.5 ("+str(d[i])+")"
        break # 初めて0.5を下回る確率になった時点で終了

結果

[1.0, 0.9972602739726028, 0.9917958341152185, 0.9836440875334497, 0.9728644263002063, 0.9595375163508884, 0.9437642969040244, 0.9256647076483308, 0.905376166110833, 0.8830518222889221, 0.8588586216782667, 0.8329752111619353, 0.8055897247675702, 0.7768974879950266, 0.7470986802363133, 0.7163959947471498, 0.6849923347034391, 0.6530885821282105, 0.6208814739684632, 0.5885616164194198, 0.5563116648347941, 0.5243046923374498, 0.49270276567601445, 0.4616557420854711, 0.431300296030536, 0.40175917986406096, 0.37314071773675794, 0.3455385276576005, 0.319031462522223, 0.2936837572807313]

30 students: 0.293683757281
When 23 students, P is lower than 0.5 (0.492702765676)


ちなみに、この問題では計算式的に d[i] はどんどん小さくなっていくので、d.sort()とかの並び替えは要らないのだ。

今回はここまで。
ザコだから未だに計算式の中に".0"入れ忘れて小数点以下切り捨てられたり、for の条件の後に":"付け忘れたりしてる…。

次:
nagoyanofes.hatenadiary.jp