見出し画像

Pythonでコラッツ予想を計算する&一部修正(3/10)

またまたpythonです。今回はコラッツ予想です。一部、記事作成から変更した場所があります。


コラッツ予想

任意の正の整数 n に対して、以下で定められる操作について考える。

- n が偶数の場合、n を 2 で割る
- n が奇数の場合、n に 3 をかけて 1 を足す

このとき、「どんな初期値から始めても、有限回の操作のうちに必ず 1 に到達する(そして 1→4→2→1 というループに入る)」という主張が、コラッツの予想である。

コラッツの問題

だそうです。これをpythonで演算します。ちなみに未解決問題です。

wikipedia曰く

2019年9月、テレンス・タオはコラッツの問題がほとんどすべての正の整数においてほとんど正しいとするプレプリントを発表し[3][4]、論文は2022年5月に出版された[5]

コラッツの問題

だそうです。数学ってやっぱよく分かんない…

設計

ざっとした設計を。

  1. パスの設定

  2. csvファイルの作成

  3. 値の入力

  4. 演算と表示

  5. グラフ出力

  6. csv出力

  7. 繰り返し(y/n)

github

import

import datetime
import sys
import os
import pandas as pd
import matplotlib.pyplot as plt

日付取得(datetime)、sys,os(なんかパスとか)、pandas(csv)、matplotlib(グラフ表示)用に色々と使用。

パスの設定

パスの設定

datetime_now = datetime.datetime.now().strftime('%m_%d_%H:%M:%S')
log_file_name = f"log_{datetime_now}"

datetime_now:実行時の時刻を月日から秒まで取得。ファイル名にするため1分以内に複数回実行した際にファイル名が重複しないように、秒まで取得。

log_file_name:csvファイル用のパス


パスの設定(最新版_24/3/10)

date_now = datetime.datetime.now().strftime('%m_%d_%H:%M:%S')

ちょっと単一の変数に複数の意味を持たせすぎたと感じたので、日付は日付として扱うことにします。今までlog_file_name変数につけていた文字列「log」は使用する際に逐一記述します。


if getattr(sys, 'frozen', False):/実行ファイル対策

if getattr(sys, 'frozen', False):
        os.makedirs(f"{os.path.dirname(sys.executable)}/csv_files/{log_file_name}", exist_ok=True)
        log_file_name = f"{os.path.dirname(sys.executable)}/csv_files/{log_file_name}/{log_file_name}.csv"
    else:
        os.makedirs(f"csv_files/{log_file_name}", exist_ok=True)
        log_file_name = f"csv_files/{log_file_name}/{log_file_name}.csv"
    with open(log_file_name, 'w') as f:
        f.write("time,value\n")

基本的にはmakedirsでcsvファイルを作成している。

pyinstallerで実行ファイルにした場合、パスの扱いがスクリプトとの際と変わるため、個別に設定。if文がFalseの場合は実行ファイルでないということ。

Trueの場合(実行ファイルの場合)

os.makedirs(f"{os.path.dirname(sys.executable)}/csv_files/{log_file_name}", exist_ok=True)

親フォルダまで作成。実行ファイルで実行されている際の処理のため、実行ファイルの親フォルダのパス(os.path.dirname)を指定し、その下に/csv_files/{log_file_name}というフォルダを作成。この際、log_file_nameは現在日付です。


ちなみにpythonで文字列""にfをつけてf""とすると文字列内で、{}で囲むことで変数を挿入できます。以下例

fuga = "fuga"
print(f"hoge{fuga}")
# hogefuga

log_file_name = f"{os.path.dirname(sys.executable)}/csv_files/{log_file_name}/{log_file_name}.csv

log_file_nameにcsvファイルまで含んだパスを代入。本当は1つのパスに複数の意味をもたすのはバグの温床になるので、推奨されないかと思うがとりあえず放置。

Falseの場合(実行ファイルでない場合)

os.makedirs(f"csv_files/{log_file_name}", exist_ok=True)
log_file_name = f"csv_files/{log_file_name}/{log_file_name}.csv"

先ほどと同じように親パスを作成。ただ今回は実行ファイルのパスとか関係なく、そのまま記述できます。またそのパスにcsvを付けます。前述の通りこういう再代入はおそらく非推奨。

ファイルの作成

with open(log_file_name, 'w') as f:
        f.write("time,value\n")

先ほどのif文内でパスを設定したので、そのままファイルを作成。


2024/03/10:新機能のため追記

後から初期値などのデータを簡単に参照できるようにtxtファイルに基本情報を載せる。

with open(f"{os.path.dirname(log_csv_file_path)}/operation_log.txt", 'w') as f:
    f.write(f"実行日時:{date_now}\n初期値:{n}\n")

最初は実行日時と初期値、最大最小値もいれときます。

with open(f"{os.path.dirname(log_csv_file_path)}/operation_log.txt", 'a') as f:
    f.write(f"演算回数:{count}\n最大値:{input_csv['value'].max()}\n最小値:{input_csv['value'].min()}")

演算

値の入力

print("値を入力してください")
n = int(input())

ユーザに値の入力を要求&変数nに値を代入(intに変換)。

変数の設定

repat = False
count = 1

repeat:繰り返し確認用の変数。初期値はFalse。
count:書き込み回数表示用。

csv書き込み

with open(log_file_name, 'a') as f:
        f.write(str(count) + "," + str(n) + "\n")
        print("\r" + str(count) + "回書き込み", end="")

作成したcsvファイルに初期値のみ追記。'a'にすることで追記モードになる。

while n != 1:
        if(n%2 == 0): #偶数
            n /= 2
        else: #奇数
            n *= 3
            n += 1
        with open(log_file_name, 'a') as f:
            count += 1
            f.write(str(count) + "," + str(n) + "\n")
            print("\r" + str(count) + "回書き込み", end="")

nが1でない限り(=nが1になるまで)繰り返す。

ここは定義通り、nがn%2 == 0がTrue(つまりnが偶数)かFalse(つまりnが奇数)の場合に分けて計算する。

計算が終了するたび、with openでcount、カンマ、値、改行文字(\n)を追記していく。その後、printでその値を表示。"\r"とend=""でその行(コンソール)を書き換え続ける。

print(f"\n{n}になりました")

おそらく1になっているはずなので、そのまま表示。

グラフ

せっかくなのでグラフ表示もしてみます。

input_csv = pd.read_csv(log_file_name)

先ほどの書き込みの終了しているcsvファイルをinput_csvに代入。

plt.plot(input_csv["time"], input_csv["value"])
plt.xlabel("time")
plt.ylabel("value")

書いたのが結構まで怪しい記憶ですが、timeよvalue(csvを作成した際の"time,value\n")を縦軸横軸として参照するように設定。

plt.savefig(f"{os.path.dirname(log_file_name)}/graph_{datetime_now}.png", format="png", dpi=300)
print(f"{os.path.dirname(log_file_name)}に画像を保存しました。")

グラフ画像の保存と保存ダイアログ(cuiでもダイアログという?)。

print("グラフを表示しますか?(y/n)")
graph_show = input()
if graph_show == "y":
    plt.show()

グラフの表示。一応、グラフの表示は任意にしました。

999をコラッツ予想で計算すると以下のようになります。1/2倍だとか3倍とかするので結構グラフの起伏が激しいですね。

一応csvも。

繰り返し確認

print("もう一度実行しますか?(y/n)")
    if input() != "y":
        break

もう一度計算するかの選択です。pythonではdo-whileがないので、while Tureで無限ループにして、繰り返さない場合(input() != y)にbreakでループから抜けてプログラムを終了します。

まとめ

テレビでコラッツ予想を見て面白そうなので、pythonでやってみました。

99999でやるとこんな感じ。

この記事が気に入ったらサポートをしてみませんか?