スキップしてメイン コンテンツに移動

functools.cache とフィボナッチ

フィボナッチ数を計算する関数を再帰で素直に書く。

def fib(n):
    if n < 0:
        return n
    return fib(n-1) + fib(n-2)

このままではものすごく遅いので、メモ化すると良い。 というのは一般常識の内だろう。

Python には 3.9 からデコレーター functools.cache があるので、簡単だ。

from functools import cache

@cache
def fib(n):
    if n < 0:
        return n
    return fib(n-1) + fib(n-2)

もう少し前のバージョンを使わなければならない人も安心して欲しい。 デコレーター functools.lru_cache が 3.2 から存在していて、lru_cache(maxsize=None) と書けば実質的に cache と同じことをしてくれる。 実は上のフィボナッチ数を計算するプログラムは functools 公式ドキュメントの lru_cache 使用例から持ってきたものだ。

話はちょっと飛ぶのだが、昔やっていた NZMATH というプロジェクトをもう一度動かそうという動きがあるとかないとか。 いや動きがあるとは聞いているのだが、実際に動き出したかどうかはまだ見えていない。 そんな NZMATH にもフィボナッチ数を計算する関数 nzmath.sequence.fibonacci があった。

FIBONACCI = {0:0, 1:1}
def fibonacci(n):
    """
    param non-negative integer n
    return the n-th term of the Fibonacci
    effect FIBONACCI[n] = fibonacci(n)
    """
    if n < 0:
        raise ValueError("fibonacci(n)  0 <= n  ?")

    if n in FIBONACCI:
        return FIBONACCI[n]

    m = n >> 1
    if n & 1 == 0:
        f1 = fibonacci(m - 1)
        f2 = fibonacci(m)
        FIBONACCI[n] = (f1 + f1 + f2) * f2
    else:
        f1 = fibonacci(m)
        f2 = fibonacci(m + 1)
        FIBONACCI[n] = f1 ** 2 + f2 ** 2

    return FIBONACCI[n]

注目したいのは2点、計算方法とメモ化だ。 計算方法は、大体半分ぐらいのところの値を使って計算するという仕組になっている。 何を参考に書いたのか判らないが、線形再帰数列の一般論から出てくるはずだ。 あるいはフィボナッチ数に特化した話としてリベンボイムの「素数の話」に書いてあったのから取ったのかもしれない。 定義通りの計算はメモ化しても O(n) 項計算するのを避けられないが、この方法は O(log n) 項の計算で済む。 一方のメモ化だが、キャッシュに使う辞書 FIBONACCI を自前で準備している。 (当時対象バージョンは Python 2.5 だったので、まだ functools.lru_cache は登場していなかった)

今ならばこの自前のキャッシュを止めて、functools.cache を使ってより簡潔に書ける。

@cache
def fibonacci(n):
    if n < 0:
        raise ValueError("fibonacci(n)  0 <= n  ?")
    if n < 2:
        return n

    m = n >> 1
    if n & 1 == 0:
        f1 = fibonacci(m - 1)
        f2 = fibonacci(m)
        return (f1 + f1 + f2) * f2
    else:
        f1 = fibonacci(m)
        f2 = fibonacci(m + 1)
        return f1 ** 2 + f2 ** 2

速さに違いがあるか timeit を仕掛けてみた、が、考えてみたら2回目以降の呼び出しはただの辞書からの読み出しだから大して意味は無いかも。 一応書いておくと、fibonacci(1000) を計算させて、元のバージョンは "2000000 loops, best of 5: 145 nsec per loop" なのに対し、 デコレーター cache を使った方は "5000000 loops, best of 5: 59.8 nsec per loop" だった。 ちなみに定義通りに書いた方はもっと手前で再帰が最大回数まで使い果たしてエラーが出る。 再帰させる行の足し算を逆転するとちょっと限界が伸びるけどね。

@cache
def fib(n):
    if n < 0:
        return n
    return fib(n-2) + fib(n-1)

このブログの人気の投稿

「函数」音訳説について探してみる

Twitter に書こうかと思ったが、ちょっと長くなるのでこっちに書くことにする。 「函数」が音訳というデマと、本当の語源 というブログ記事について ツイートされてるの を見た。 詳しい内容はリンクを辿って読んでみて欲しい。 その記事中の「函数」音訳説という部分で、引用されているものが意外と新しい1980年代以降のものなので、さすがにもっと古いだろう、と探してみた。 1. 武部良明「漢字の用法」(1976) このほか武部良明『漢字の用法』(角川書店1976)にも同様の記述があるという(未確認)。 とあって、たまたま同書(三版)を持っているので調べてみた。 「関数」ではなく「関・函」の項にあった。 「函」は「いれる」意味。 したがって、「函数」という漢字の組み合わせからは、「対応して定まる数」という意味は出てこない。 「函数」については function という原語の音 fun を、「函」の音カン(現代中国語 han)で写したものとされている。 これに対し現代表記の「関数」は、全体の意味を「かかりあう数」と考え、「函」の部分を、同音で「かかりあう」意味の「関」に書き換えたものである。 2. 遠山啓「関数を考える」(1972) 近所の図書館で見つけた。 会話体の文章の中で、先生役が言っている。 はっきりはわからないが, 中国語の発音では function と似ていて, しかも意味も近いらしいのだ. 3. 遠山啓「数学は変貌する」(1971) 2012年にちくま学芸文庫から出た「現代数学入門」所収。 「関数を考える」が中学生ぐらいを対象にしたシリーズの一冊なので、そこでいきなり初披露ということは無いだろうと見当をつけて遡って探した。 機能とは簡単にいうと働きです。 ライプニッツが初めてファンクションという言葉を使った. ライプニッツはドイツ人ですが, ドイツ語は当時はいなかの言葉みたいで, フランスが文化の中心であったから, フランス語で書いてありますので, フォンクション (fonction) です. もとは日本では「函数」, あとになって「関数」と改めたのです. なぜこんな函の数という妙な言葉を使ったか, これは中国からきた字です. 中国人は各国語を音まで似せて訳すことがうまい. フォンクションは中国語で函数を中国読みしますと非常に似ているのだそ...

2025年の読書

読書メーターの記録 によるとマンガも入れて101冊。 久しぶりに年100冊に乗った。 前年からの勢いのままに菌類関係の本をいくつか読んだが深澤遊の「キノコとカビの生態学」「枯木ワンダーランド」は面白かった。 後半はファーブル昆虫記を読破した。岩波文庫の古い訳で読んだのが良かったのか良くなかったのか。 数学関係は、前半表現論の本を読んで表現とは加群のことであるという言葉で何だかすっきりした。 年の終わり近くはラングランズ予想というか楕円曲線と保型形式とガロワ表現とみたいな内容の本をいくつか。「ラングランズ予想」というそのものズバリなタイトルの本も出たのでその内読みたい。 マンガは、「ヴィンランド・サガ」も「アルテ」も「推しが武道館に行ってくれたら死ぬ」も完結して、読み続けるタイトルが少なくなってきた。

「函数」音訳説について探してみる (まとめ)

これは 「函数」音訳説について探してみる 「函数」音訳説について探してみる (辞書編) の続きになるので、まずはそちらを読んでもらえると解りやすいと思う。 ざっくり言うと、函数は function の中国における音訳、という俗説( 「函数」が音訳というデマと、本当の語源 参照)の起源を求めて、できるだけ古い言及を探そうというクエストの記録。 先に結論から言うと、遠山啓「キュート数学I」(1967)ついで「角川新字源」(1968)という1960年代末発行の二つが見つかった。 遠山啓 既に「関数を考える」(1972)と「数学は変貌する」(1971)は見つけていたが、さらに古いものが見つかった。 「キュート数学I」(1967)だ。 元は三省堂から出版されたもののよう(実物は見ていない)だが、二度形態を変えて再度刊行されている。 一つめが太郎次郎社の遠山啓著作集シリーズの「数学教育論11 数楽への招待I」(1981)、 もう一つがソフトバンククリエイティブの「基礎からわかる数学入門」(2013)だ。 先生●それには歴史的な事情があるのだ. ヨーロッパの数学が中国に伝えられたとき, function は「函数」と訳された. 中国語ではやはり function の意味をもっているが, 同時に音も似ているらしい. それがそのまま日本に伝えられ, 「函数」となり, 「函」が当用漢字にないので, それがさらに「関数」になったわけだ. 引用は「基礎からわかる数学入門」によるが、「数楽への招待I」でも約物の違いぐらいで内容は変わらない。 角川新字源 既に辞書編で書いたように、新字源は1968年の初版から音訳説を採っていると思われる。 若干の歯切れの悪さは、Google Books にある1978年発行のもののスキャンより古い時代のものを当たった訳ではないので、 細かい修正の一部として書き直された可能性が捨てきれないことによる。 (スキャンでない現物は近所の古本屋で1989年発行のものだけは見ることができ、1978年のものと同じ文言だった。) その他 「広辞苑」は辞書編で第五版(1999)まで遡ったが、その後第四版(1991)も「函数」の項で音訳説を紹介しつつ「関数」に飛ばす、という同じ内容であることを確認した。 期待して第三版(1983)を調べたら、こちらは音訳説無し...