乙Py先生のプログラミング教室
初学者のためのプログラミング学習サイト

数学

今回はSNSで話題になっていた
直感に反する数学の問題を解く
プログラムに関してです。



解説動画はこちら 



今回の問題は、とある数学者の考案した
「一見シンプルだが直感に反する確率パズル」
というものです。


問題

100個のボールが入ったつぼがあります。
そのうちn個が赤で、100-n個が緑です。
ただし、nは0~100の間で一様分布しています。

つぼからボールをランダムに1つ取り出したところ、赤でした。
それを捨ててから、残り99個からボールを選ぶとき
次に引くボールの色は赤と緑どちらが多いでしょうか?

1.赤
2.緑
3.赤と緑が同じ



さてこの問題を考えてみてください。





解説

こちらの詳細な解説は
ながーいので、割愛します。

動画の中では 3:53 くらいから解説しています。



シミュレーションコード

こちらの確率をもとめるコードは
こんな感じになります。

1.ランダムに赤いボールの個数を決める
2.赤を引いたら、次のボールを引く
3.二回目のボール、赤と緑どちらを引いたか集計する

import numpy as np

# シミュレーションの設定
num_trials = 100000  # 試行回数
n_balls = 100  # ボールの総数
data = []

# 結果を記録するための変数
red_first_count = 0
red_second_count = 0
green_second_count = 0

# シミュレーション
for _ in range(num_trials):
    n_red = np.random.randint(0, n_balls + 1)  # 0から100までのランダムな赤いボールの数
    n_green = n_balls - n_red  # 緑のボールの数

    # 最初のボールをランダムに引く(赤または緑)
    first_draw = np.random.choice(['red'] * n_red + ['green'] * n_green)

    if first_draw == 'red':
        # 最初に赤を引いたので、赤を1つ減らす
        red_first_count += 1
        n_red -= 1

        # 次のボールを引く
        second_draw = np.random.choice(['red'] * n_red + ['green'] * n_green)

        # 次のボールの数を集計
        if second_draw == 'red':
            red_second_count += 1
        else:
            green_second_count += 1
        tmp = [red_first_count,red_second_count,red_second_count/red_first_count,n_red]
        data.append(tmp)

# 結果の計算
total_first_red = red_first_count
total_second_red = red_second_count

# 確率の計算
if total_first_red > 0:
    p_second_red_given_first_red = total_second_red / total_first_red
    print(f"P(second red | first red) = {p_second_red_given_first_red:.4f}")
else:
    print("No trials with the first ball red.")



google colab などで試してみてください。

ベイズの定理などを用いて
机上でかなり大変な計算をしないと
求めることは困難ですが

プログラムであれば
簡単にシミュレーションして
擬似値をもとめる事ができます。

ぜひ色々試してみてください。

それでは。


たまには真面目に
数学の問題を解いてみましょう

2023年度の東大の数学の問題です


解説動画はこちら



今回は数学の問題です

手で解いたら解法が分からず
すごく時間が掛かってしまう問題も
プログラミングの力を使えば
近似解を簡単に求める事ができます

という訳で
東大の数学の問題を解いてみましょう





2023年度東大数学文系第3問

問題:
黒玉3個、赤玉4個、白玉5個が入っている袋から
玉を1個ずつ取り出し、取り出した玉を順に1列に12個並べる
ただし、袋から個々の玉が取り出される確率は等しいものとする

(1) どの赤玉も隣り合わない確率 p を求めよ
(2) どの赤玉も隣り合わないとき
どの黒玉も隣り合わない条件付確率 q を求めよ


12個の順列の問題ですね
プログラミングの力を使えば
こういうのもシミュレーション出来ます


考え方

まずは石を用意しましょう
次にその石の並びで隣同士なのかどうかを
チェックする方法を考えてみましょう

次のようなやり方で
石のデータと判定方法を
考える事ができます
# 12個の玉を作って試してみる
import numpy as np

data = ["b"]*3 + ["r"]*4 + ["w"]*5
np.random.shuffle(data)

# リストを結合して文字列にする
st = "".join(data)

# 赤玉が隣り合わないかどうか
res1 = "rr" not in st
# 上記のに加えて黒玉が隣り合わないかどうか
res2 = res1 & ("bb" not in st)

print(st)
print(res1)
print(res2)
rwrwwbbrbwwr
True
False

データは文字列やリスト型で定義しておくと
後で計算しやすいです

これを元にして
計算を行ってみましょう



順列を生成してチェックする方法



今回は12個の中から12個を取り出してつくる
順列になるので、その総個数は
import math

math.factorial(12)
479001600


順列は itertools ライブラリの
permutations で作る事ができます

チェック機構では判定式を返し
Trueを1、Falseを0として
条件にあった個数を返していきます

最終的に条件にあった個数と
元の順列の個数を組み合わせれば
確率が求められます
import itertools

def num_check(data):
    st = "".join(data)
    res1 = "rr" not in st
    res2 = res1 & ("bb" not in st)
    return res1, res2

data = ["b"]*3 + ["r"]*4 + ["w"]*5

ans1, ans2 = 0, 0
for t in itertools.permutations(data):
    t1,t2 = num_check(t)
    ans1+=t1
    ans2+=t2

# 総当たりの個数(順列の個数)
permutations_num = math.factorial(12)

print("1の答え : {0}".format(ans1/permutations_num))
print("2の答え : {0}".format(ans2/ans1))
1の答え : 0.2545454545454545
2の答え : 0.6130952380952381




シミュレーションしてみる


総当たりの方法が思いつかなくても
シミュレーションで近似解を求める事ができます

先ほどは順列を作って繰り返しを行っていましたが
単純に回数を指定して、ランダムに並び替えた後に
チェックを行えば、順列の際の近似が求められます

チェック関数は先ほどのを用いるとして
次のような計算でシミュレーションできます

import numpy as np

N = 100000000

ans2_1, ans2_2 = 0, 0
for i in range(N):
    data = ["b"]*3 + ["r"]*4 + ["w"]*5
    np.random.shuffle(data)
    t1,t2 = num_check(data)
    ans2_1+=t1
    ans2_2+=t2

print("1の答え : {0}".format(ans2_1/N))
print("2の答え : {0}".format(ans2_2/ans2_1))
1の答え : 0.25452006
2の答え : 0.613167347202417


小数点第3位くらいまでは
同じ数字になりますね

近似解だと点数は貰えないかもしれませんが
点数関係ない、仕事上の計算であれば
こういったシミュレーションが行えるのが
プログラミングの役に立つポイントです

確率が分からなくても
それっぽい確率を算出する事ができるので
いろいろ便利ですねえ

色々なシミュレーションを行えるので
仕事がめちゃくちゃ捗ります

プログラミング出来ない方は
是非、この機会に覚えてみて下さい


今回は数学の問題を解く
プログラミングでのシミュレーションでした
それでは
 

今回はプロジェクトオイラーの
数学の問題を解いてみました

解説動画はこちら


 
さて
プロジェクト・オイラー

は、英語で書かれた
数学的/コンピューター プログラミングの問題集です
リンクから色々な問題をみる事ができます

レオンハルト・オイラーは
18世紀の数学者・天文学者のことですね

今回はこれを解いていきたいと思います
数学の問題をプログラムを
駆使して解いてみましょう

問題を考えたい人は
動画を止めて考えてみて下さい

解答そのものは動画をご覧ください






問題1

1000未満の3か5の倍数の合計値を求めよ






・・・・・

・・・・

・・・

・・










解答1

比較的簡単な問題です

1000未満の数値の生成
3と5の倍数の判定
合計値を求める

これらが達成出来ていれば
解けると思います

sum([i for i in range(1,1000) if i%3==0 or i%5==0])





問題2

400万未満のフィボナッチ数で

偶数の物の合計値を求めよ

フィボナッチ数とは

1, 2, 3, 5, 8, 13, 21, 34, 55, 89 のように

どの数字も前2つの数字を足した数字のこと




・・・・・

・・・・

・・・

・・



解答2

フィボナッチ数を求める関数を作って
400万までのリストを作り
偶数のものを抜き出せば解けます

フィボナッチ数のリストを返す関数
# その数までのフィボナッチ数を求める
def fib(num):
    a , b , fib_l = 0 , 1 , []
    while b <= num:
        fib_l.append(b)
        a , b = b , a+b
    return fib_l

sum(([f for f in fib(4000000) if f%2==0]))

ジェネレーター式でも同様に出来ます
# ジェネレーター形式
def fib_gen(num):
    a , b = 1 , 1
    while b <= num:
        yield b
        a , b = b , a+b
        
sum(([f for f in fib_gen(4000000) if f%2==0]))




問題3

600851475143 の最大の素因数を求めよ

素因数分解をして、その最大値を求める


・・・・・

・・・・

・・・

・・






解答3

素因数分解をする手法はたくさん有りますが
中でも簡単な試し割り法を使って
素因数分解してみましょう

素因数分解しようとする整数nを
小さい順に割ってみて
割り切れるかどうかを調べる手法です


num = 600851475143

# 3から奇数で割っていく
f , a = 3 , []
while f * f <= num:
    
    # 割り切れたら素因数に採用
    if num % f == 0:
        a.append(f)
        
        # 次の数は、素因数で割った数で探す
        num //= f
    
    # 割り切れなければ次の素数へ
    else:
        f += 2
        
if num != 1:
    a.append(num)

# 素因数の組み合わせ
print(a)

# 素因数の最大値 
print(sorted(a,reverse=True)[0])


なお、ライブラリを用いれば
簡単に素因数分解できてしまいます
# ライブラリで素因数分解
import sympy

num = 600851475143
sympy.factorint(num)





問題4

2 つの 3 桁の数の積から作られる

最大の回文数値を求めよ

101*101 = 10201 のような

回文になる数値の最大値を求める


・・・・・

・・・・

・・・

・・






解答4

数値を文字列に直してひっくり返せば
回文の出来上がりです

answer = {}

for a in range(100,1000):
    for b in range(100,1000):
        s_num = str(a * b)
        if s_num == s_num[::-1]:
            answer[a * b] = [a,b]

for k,v in sorted(answer.items(),reverse=True):
    print(k,v)
    break








問題5

1 から 20 までのすべての数で割り切れる

最小の正の数を求めよ

最小公倍数を求める問題


・・・・・

・・・・

・・・

・・







解答5

最小公倍数は
次の様な計算式で求められます

最小公倍数 = 数値の積 ÷ 最大公約数


最大公約数を求めるには
ユークリッドの互除法を用います

ユークリッドの互除法は
2つの自然数の最大公約数を求める
手法の一つですが
比較的実装が楽です

# ユークリッドの互除法
# 最大公約数を求める
def gcd(a, b):
    if a == 0:
        return b
    m = b % a
    return gcd(m, a)

# 最小公倍数 = 数値の積 / 最大公約数
from functools import reduce

# 最小公倍数を計算する(2つ)
def lcm_base(x, y):
    return (x * y) // gcd(x, y)

# 最小公倍数を計算する(複数)
def lcm(*nums):
    return reduce(lcm_base , nums, 1)


s = [i for i in range(2,21)]
# 最小公倍数をリストで求める際は * を付ける
lcm(*s)



python3.8以降なら
ライブラリでも求められます
# 最小公倍数を求める
import math
s = [i for i in range(2,21)]

# 最小公倍数をリストで求める際は * を付ける
math.lcm(*s)






問題6

100 個の自然数の

和の 2 乗と 2 乗の和の差を求めよ

例1,2,3

(1 + 2 + 3)^2 − (1^2 + 2^2 + 3^2) 

= 36 −14 

= 22



・・・・・

・・・・

・・・

・・






解答6


和の2乗は比較的楽に計算できますが
2乗の和は少しコツがいります

map関数は関数を引数に指定できるので
リストの要素に
関数を適応した結果を求めるのに
すごく役立ちます
nums = [i for i in range(1,101)]

# 和の2乗
sum_square = sum(nums) ** 2
print(sum_square)

# 2乗の和
sum_of_squares = sum(map(lambda n : n**2 , nums))
print(sum_of_squares)

# 2乗の和と和の2乗の差
print(sum_square - sum_of_squares)





問題7

10001 番目の素数を求めよ





・・・・・

・・・・

・・・

・・





解答7

素数を求めるのは流石に面倒くさいので
ライブラリの力を借りましょう

isprimeメソッドは
実行速度がめちゃくちゃ速いので
大抵の人が実装したものよりも
速いだろうと思います
# 素数を求めるライブラリ
from sympy import isprime

primes = [2,3]
for i in range(5,200001,2):
    if isprime(i):
        primes.append(i)
    if len(primes)==10001:
        answer = i
        print(answer)
        break


ライブラリでその数値までの
素数の個数を数える事も出来ます
# ライブラリで素数の個数を数える
from sympy import primepi

print(primepi(answer))







問題8

次の1000 桁の数の中で

最大の積を持つ隣接する13桁は何か

数値は下記をお使いください

73167176531330624919225119674426574742355349194934
96983520312774506326239578318016984801869478851843
85861560789112949495459501737958331952853208805511
12540698747158523863050715693290963295227443043557
66896648950445244523161731856403098711121722383113
62229893423380308135336276614282806444486645238749
30358907296290491560440772390713810515859307960866
70172427121883998797908792274921901699720888093776
65727333001053367881220235421809751254540594752243
52584907711670556013604839586446706324415722155397
53697817977846174064955149290862569321978468622482
83972241375657056057490261407972968652414535100474
82166370484403199890008895243450658541227588666881
16427171479924442928230863465674813919123162824586
17866458359124566529476545682848912883142607690042
24219022671055626321111109370544217506941658960408
07198403850962455444362981230987879927244284909188
84580156166097919133875499200524063689912560717606
05886116467109405077541002256983155200055935729725
71636269561882670428252483600823257530420752963450






・・・・・

・・・・

・・・

・・





解答8


まずは文字列をデータ化しますが
きちんとゴミを取り除いておきましょう

文字列を数値に直して
計算すればOKです

総積を求めるのは
for文を使っても良いですが
reduce関数などを使って
まとめる事ができます


data = '''
73167176531330624919225119674426574742355349194934
96983520312774506326239578318016984801869478851843
85861560789112949495459501737958331952853208805511
12540698747158523863050715693290963295227443043557
66896648950445244523161731856403098711121722383113
62229893423380308135336276614282806444486645238749
30358907296290491560440772390713810515859307960866
70172427121883998797908792274921901699720888093776
65727333001053367881220235421809751254540594752243
52584907711670556013604839586446706324415722155397
53697817977846174064955149290862569321978468622482
83972241375657056057490261407972968652414535100474
82166370484403199890008895243450658541227588666881
16427171479924442928230863465674813919123162824586
17866458359124566529476545682848912883142607690042
24219022671055626321111109370544217506941658960408
07198403850962455444362981230987879927244284909188
84580156166097919133875499200524063689912560717606
05886116467109405077541002256983155200055935729725
71636269561882670428252483600823257530420752963450
'''.replace('\n','')

import operator
from functools import reduce

answer = {}
for i in range(0 , len(data)-12):
    nums = data[i : i+13]
    if '0' in nums:
        continue
    
    # 総積を求める
    a = reduce(operator.mul, [int(n) for n in nums],1)
    answer[a] = nums
    
for k,v in sorted(answer.items(),reverse=True):
    print('数値 :  {0} , 文字列 : {1}'.format(k,v))
    break







問題9

a < b < c で a + b + c = 1000である時

ピタゴラスのトリプレットとなる数値の

 a * b * c の値を求める

ピタゴラスのトリプレットとは

a^2 + b^2 = c^2






・・・・・

・・・・

・・・

・・




解答9

a<b<c かつ a+b+c=1000 なので
abcの数値の最大値はおのずと決まってきます

あとはピタゴラスの定理のような
関係性になる数値を探し当てれば
それが答えです


# aの最大値は1000の3分の1より小さい
for a in range(1, 332):
    
    # bの最小値は a+1、最大値は 1000から a を引いた半分
    for b in range(a + 1 , (1000 - a)//2):
        c = 1000 - (a + b)
        if a**2 + b**2 == c**2:
            print(a * b * c , a , b , c)
            break
    else:
        continue
    break





さて、ここまでが今回の内容です
なかなか面白い問題ばかりですねえ


まだまだプロジェクトオイラーには
たくさんの問題が残っているので
これからも問題を解いていきたいと思います

それでは

今回は結構辛目な分数の問題を
プログラムで解いてみることにしました。


解説動画はこちら



さて、今回の問題は2022年の
市川高校という所の数学の問題のようです。 

スクリーンショット 2022-02-05 16.06.53

見てるだけでも嫌になりそうな問題ですね!!!

でもこれ、高校入試の問題だから
中学生が解いてるわけですよ

くーーー
私中学生以下ですねーー

悔しいのでプログラムの力を借りましょう。
Python言語では分数の計算を
手軽に行うことのできる
「fractions」というライブラリがあります。

これを使って分数の計算をしてしまいましょう。
ライブラリを読み込んで最初の部分の
計算をしてみましょう。
from fractions import Fraction

a = Fraction(1,6)
print(a)
1/6

Fraction(分子,分母)という形で
分数を表すことができます。

残りの部分も入力して行きましょう。
長いので変数に分割して行きます。
s1 = 4**4 + 4*  3**4
s2 = 4**4 + 4*11**4
s3 = 4**4 + 4*19**4
s4 = 4**4 + 4*27**4
s5 = 4**4 + 4*35**4

b1 = 4**4 + 4*  7**4
b2 = 4**4 + 4*15**4
b3 = 4**4 + 4*23**4
b4 = 4**4 + 4*31**4
b5 = 4**4 + 4*39**4

s = s1*s2*s3*s4*s5
b = b1*b2*b3*b4*b5

c = Fraction(s,b)
print(c)
1/337


最後の部分は
この2つを掛け合わせるだけですね!!
print(a * c)


はい、今年の入試問題にふさわしい
問題ではないでしょうか!!


っていうか、こんな数になる数式を
考え出した設問者がすごい!!!!!!

解く方はカンでもいけるかもしれないけど
こういう問題を作る方は凄いですね

今回は辛目な分数の問題を解いてみました。
Fraction、すっごく使えますよ
是非使ってみて下さい。

それでは。

今回はTwitterで出回っていた
電車の中吊り広告にあった
数学の問題をプログラムで解いて見ました。

解説動画はこちら



さて、問題は・・・

GAKKOUの6文字を並び替えてできる
360個の文字列を辞書式に並べるとき
100番目の文字列を求めよ


ということで
マジで難しい!!!

自分如きでは見当もつきません!!

こんな時はプログラムの力を借りて
解いてみることにしましょう。


Pythonでは文字列の組み合わせを作ってくれる
ライブラリがあるのでこれをimportしておきます。
import itertools

まず、データとして文字列を
リスト型で定義しておきます。
s = ['G','A','K','K','O','U']

次に文字列の組み合わせを作ります。
辞書形式ということですが
ここでは重複排除のできるSET型で
文字列の組み合わせを作ります。

itertools.permutations(データ,個数)
で順列を求めることができます。

第一引数は先ほど作った文字列のリスト
この中から6個の文字を取るので
第二引数は6になります。
res = {a for a in itertools.permutations(s,6)}

これで文字列の組み合わせが出来上がったので
最後に並び替えですね。

並び替えはsorted関数が使えます。
リスト形式にしてインデックス値の99番目が
100番目にくる文字列になるはずです。
''.join(list(sorted(res))[99])

答えは

・・
・・・


受験生にとって一番良い答えですね


Python言語はコードを書く量が少なくて済むのが
利点の一つでもあります。

このコードも1行で出来るようにしてみましょう。
こんな感じになりました。
import itertools;sorted({''.join(r) for r in itertools.permutations('GAKKOU',6)})[99]

はい

他の言語でもだいたいは1行でいけちゃいますね。
どの言語が書く文字数が少ないのか
調べて見たいものですね。

プログラムできる方は
是非考えて見て下さい。


今回はここまでです
それでは。

このページのトップヘ