見出し画像

Pythonでフィボナッチ数列を書いてみた

どうも今回はpythonでフィボナッチ数列を吐き出すプログラムを書いてみた話です。

お断り

自分は素人もいいところのプログラミング初心者です。企業やグループでの開発経験もないです。そのためおかしな処理、非効率な処理、おかしな命名規則をしているかもしれませんがご了承ください。またかなり細かく説明するの結構長いです。


フィボナッチ数列とは

フィボナッチ数列(フィボナッチすうれつ、(: Fibonacci sequence) (Fn) は、次の漸化式で定義される:
F0 = 0,
F1 = 1,
Fn+2 = Fn + Fn+1 (n ≥ 0)
第0~22項の値は次の通りである:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, …(オンライン整数列大辞典の数列 A000045
1202年にフィボナッチが発行した『算盤の書』(Liber Abaci) に記載されたことで「フィボナッチ数」と呼ばれているが、それ以前にもインドの学者であるヘーマチャンドラ (Hemachandra) が韻律の研究により発見し、書物に記したことが判明している[1][2]

フィボナッチ数

だそうです。要は前の値とその値を足した値が次の値になる数列のことです。

まずは大まかな流れ

大体こんな感じを予想していました。

  1. 数列を入れる配列を作成

  2. 配列に対してi番目の値にi+1番目の値を足す

  3. 2の値を配列の最後に挿入

  4. 2,3を繰り返す

  5. ファイルに配列を書き込む

ファイルはcsv

初めはtxtにしていたのですが、もし今後何かに活用するならcsvの方が解析とかもしやすそうなので。あと書き込みも簡単なので。

import

import datetime # 日付取得 
import os # makedirs
import sys # 何用だっけ?

モジュールだとかライブラリの読み込みです。pythonはまともに触ったのは初めてなのでよく知りません。今回はおまじないとして使います。

機能の選択などの設定

    #設定の入力
    print("1. n番目を求めますか?\n2. n番目番目まで全ての値を求めますか?\n1か2を入力してください。")
    All_cal = False
    chosen_input = input()
    if chosen_input == "1":
        All_cal = False
    else:
        All_cal = True
    print("nの値を入力してください。")
    time_cal = int(input())

各変数の意味

All_cal…出力の際に利用します。Falseなら全てを書き込まず、Trueなら全てをcsvに書き込みます。
chosen_input…ユーザの選択の入れ先。
time_cal…計算回数

print("1. n番目を求めますか?……")

これは「n番目の値を取得する」機能と「n番目までの全ての数列をcsvに書き込む」という2つの機能を持たせたため、ユーザに選択をさせます。ちょっとこの辺りのbool値の扱いが直感的でないので、いつか修正したい。

chosen_input = input()でユーザの選択を入力します。

if chosen_input == "1":

if文で"1"が入力されたのかそれ以外かを判定します(1と2以外でもとりあえず進む様に)。1でも一応そのままAll_cal変数をFalseにします(最終的にプログラム全体をループさせるのでTrueになっている可能性がある。なりませんね。でも面倒なのでスルー)。2が入力された場合は全て書き込むのでTrueにします。


2024/02/18
上記の修正。本来ならこれでよかった。ループしても宣言時にFalseしてるので、問題はなかった。ただif文で真の時の処理を書かないでいきなりelse書くとエラーになるので、!= "1"とする。

if chosen_input != "1":
    All_cal = True

time_cal

そしてtime_calという計算回数を決定する変数を宣言します。ここでもユーザに入力を求めます。

演算部

list_fibonacci = [0,1,]
count = 1
for i in range(time_cal - 2):
    list_fibonacci.append(list_fibonacci[i]+list_fibonacci[i + 1])
    i -= 1
    print("\r" + str(count) + "回演算", end="")
    count += 1
print("演算終了")

各変数の意味

list_fibonacci…配列ですが一応。演算した値を入れてく箱
count…一応処理中の表示用に

配列の宣言

pythonでは「配列名 = [要素0,要素1]」で宣言できます。

list_fibonacci = [0,1,]

最初は値を与えないといけないので、0と1だけ入れておきます。

count = 1

countを1にしておく。for文の最後でインクリメントしておきます。

for i in range(time_cal - 2):

for文で演算と挿入を繰り返します。繰り返す回数はユーザが選択した数にするため、演算のないfibonacci[0,1,]の2回分を引いておきます。

配列の添字はiを利用します。ただ2つ目の値を取得する際にiをインクリメントしているので、その後i -= 1で戻しておきます。こうしないとiがfor文によってもインクリメントされ回数がずれます。

print("\r" + str(count) + "回演算", end="")

これは進捗状況の確認用です。正直演算は爆速なので一瞬です。ただ一応処理落ちとかの確認ができます。\rはその行の先頭に戻る、end=""は改行しないを意味して、同じ行にstr(count)(count変数をstr化)を繰り返し表示させます。

書き込み部

if All_cal == False: # 1.n番目を抜き出す
    print(list_fibonacci[len(list_fibonacci) - 1])
else: # 2.全ての値を求める
    # 書き込み
    count = 1
    for n in list_fibonacci:
        write_csv(n, count, csv_file_title)
        count += 1
    print("\n書き込み完了")

ここでAll_cal変数の内容で分岐します。

if All_cal == False: # 1.n番目を抜き出す

All_calがfalseということはn番目だけでいいので配列の最後の値がわかればいい。したがってlen()で配列の全要素数を取得して1を引いた値を添字として、配列の最後の値を表示する。ここで-1するのは配列の添字は0が先頭だがlen()で取得したサイズは1から数えた数のため。

else: # 2.全ての値を求める

All_calがtrueということは全ての値をcsvに書き込みます。
ここでまたcount変数を宣言します。これは書き込みの進捗表示用です。

ここで書き込み機能は別関数にします。またwrite_csvの行の最後にcsv_file_titleという宣言してない変数がありますがこれはパス関連の問題で別の場所で宣言しているのでそちらの説明をします。

パスの設定

実は機能の選択などの設定のすぐ下にあるコードなのですが、複雑になりそうなので後回しにしました。

csv_file_title = f"fibonacci_{datetime.datetime.now().strftime('%m_%d_%H:%M:%S')}.csv"
# パスの設定
if getattr(sys, 'frozen', False):
    executable_path = sys.executable
    parent_path = os.path.dirname(executable_path)
    # ファイルフォルダ作成
    os.makedirs(f"{parent_path}/csv_files", exist_ok=True)
    csv_file_title = f"{parent_path}/csv_files/{csv_file_title}"
    with open(csv_file_title, 'w') as f:
        f.write("time,value\n")
else:
    os.makedirs("csv_files", exist_ok=True)
    csv_file_title = f"csv_files/{csv_file_title}"
    with open(csv_file_title, 'w') as f:
        f.write("time,value\n")

csv_file_title = ……}.csv"

これはさっきのcsv_file_title変数の定義ですね。これはcsvファイルを作成するパスを示しています。

if getattr(sys, 'frozen', False):

これは今実行しているのが.pyファイルなのかexeなどの実行ファイルになっているを判定しています。(多分)もし実行ファイルの場合はどうやらパスの指定を変えないといけないそうなので、それを実装していきます。

executable_path = sys.executable

これはexecutable_path変数にその実行ファイルのパス(sys.executable)を代入しています。


2024/02/18
parent_pathに直接、sys.executableを代入。

parent_path = os.path.dirname(sys.executable)

parent_path = os.path.dirname(executable_path)

csvファイル用のフォルダは実行ファイルのある階層に作成したいので実行ファイルの親フォルダのパスを取得します。os.path.dirname(executable_path)これで()内のファイルの親フォルダを取得できる。


2024/02/18
All_calがFalse(n番目のみ表示する)な時にフォルダを作成しないように以下のように修正。

if All_cal == True:
    os.makedirs(f"{parent_path}/csv_files", exist_ok=True)
    csv_file_title = f"{parent_path}/csv_files/{csv_file_title}"
    with open(csv_file_title, 'w') as f:
    f.write("time,value\n")

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

os.makedirsというのはフォルダを作成する機能です。基礎構文は以下の通り。

os.makedirs("パス",)
# 既にフォルダが存在する場合でもエラーを起こさないように以下の引数をつける
os.makedirs("パス", exist_ok=True)

ちなみにmakedirsは中間フォルダも再起的に作成するため、便利だそう。

f"{parent_path}/csv_files"とは

文字列を示す""の前にfがついているのは文字列内で変数を扱うための記述です。この場合、{}で囲った範囲は変数として扱えます。今回は先ほど実行ファイルのある親フォルダのパスに/csv_filesというフォルダを作成しています。

csv_file_title = f"{parent_path}/csv_files/{csv_file_title}"

これは先ほどの親パスにcsvファイルの名前も追加したcsvのパスになります。(_titleになっているのにパスが入るのはちょっとアレかもしれませんが…)ここで最後の{csv_fiel_title}にはfibonacci_日付が入っています。

with open(csv_file_title, 'w') as f:

with文はファイル操作や通信などの開始時の前処理と終了時の後処理など必須となる処理を自動で実行してくれるものになる文だそう。

上記のcsv_file_titleというタイトルのファイルを作成(最後が.csvになるためファイル形式はそのまま)します。'w'は上書きという意味です。ちょっと自分でもよく分からないのですが、ファイル名には親フォルダのパスなどが入らず「fibonacci_02_17_15:33:57.csv」となっています。よく分からん。おそらく、パスを含んだ文字列ではなく、パスのデータとして扱っているから最後の/以降がファイル名とし設定されてます。

f.write("time,value\n")

fとした物にwriteで()を書き込みます。今回は"time,value\n"というカラムタイトルをファイルの初期化の意味も含めて実行します。\nは改行の意味です。

else:

elseということは.pyファイルを実行しているのでそのままフォルダを作成できます。先ほどとは違い、csv_filesというフォルダをそのまま作成しています。それ以外は同じです。

os.makedirs("csv_files", exist_ok=True)
csv_file_title = f"csv_files/{csv_file_title}"
with open(csv_file_title, 'w') as f:
    f.write("time,value\n")

このコードの必要性

これは書き込むファイルやフォルダのパスを設定しているのですが、本来ならここまで多くはなりません。ただし今回はpyinstallerというツールでpythonのない環境でも動作する単一の実行ファイルを作りました。これの影響でファイルが作成されない問題が発生したため、パスを明確に設定しています。


このあとは演算部につながっています。

書き込み部2(write_csv関数)

これで書き込み部の章での最後の謎変数「csv_file_title」がcsvファイルのパスを示していることがわかりました。ではいよいよ書き込みです。

pythonではそのコードより上にある関数を使用するので、こんか所謂main関数的な処理(ここまでの処理)は直に書いてます。importたちと今まで処理の間に以下を突っ込みます。

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

def write_csv(n, count, file_name):

関数の定義です。pythonの関数は以下の通り。cなどの{}はインデントで行うよう。また引数も型の宣言が不要なよう。

def 関数名(引数1, 引数2):
    //処理
//関数外の処理

(n, count, file_name)

今回使ってる引数は上の3つ。nは書き込む数列の値、countは何番目か用の番号です。file_nameはファイルに書き込む時のファイルの特定用。

with open(file_name, 'a') as c:

先ほどの様にファイルに対してopenします。今回は'a'としています。これは追記の意味で、ファイルの初期化で書いた"time,value\n"を消さないように、さっき書いた1個前の値を消さない様にするためです。

countのインクリメントは関数の呼び出し元である書き込み部で行なっています。

print("\r" + str(count) + "回書き込み", end="")

これは書き込みの進捗を表示します。演算に関しては一瞬なのですが、nを10000とかにすると結構遅くなります。なので書き込みの進捗は確認しやすい方がいいです。

最初のwhile

順番的にはかなり前のコードなのですが、最後の方がわかりやすいので。

while True:
    //全体の処理
    print("もう1度計算しますか?y/n")
    if input() != "y":
        break

普通ならdo~whileで少なくとも1度は実行を実装するのですが、pythonにはdo~whileがないので、while Trueで無限ループさせ、ifで抜けるという方法を取ります。

print("もう1度計算しますか?y/n")でy以外(!= y)が入力さればbreakして終了します。

まとめ

長々とお付き合いいただきありがとうございました。

今回はいつものようにプログラミング言語の基礎の話はしなかったので、いつかpythonシリーズも作りたいなぁ~と思ってます。それと最初はswiftで書いたんですけど、型の関係で大きな値を扱いづらかったのでpythonにしました。やっぱり言語ごとに向き不向きありますね。pythonはアルゴリズムというか処理の方法に集中できる印象です。

一部修正点まとめ(最終追記:2024/02/18)

  • All_calの設定を最適化

  • sys.executableの親フォルダを直接代入

  • n番目のみ表示する際にフォルダが作成される問題を修正

githubで公開

一応noteで晒したんだから公開しても変わらんかと思い、publicにしようと思います。過去のバージョンのコードがひっどいのであれですが一応。というかコミットの仕方とかgitの使い方がわからず無茶してます。

参考

↑のページだけでなく関数などの解説も参考にさせていただきました。

https://www.perplexity.ai/

その他多くの解説サイト様、ありがとうございます。

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