すべてのソリューションを生成するコードがあるとします。
(ここでパラメータzには 9 を渡します。これは不等式の右側の数値です。このコードはrが正の場合にのみ機能することに注意してください。)
from math import floor, ceil
def iter_solutions(r, n, z):
c = [None] * n
def iter_solutions_bounded(k, pick):
# pick is the last pick, if any, and 0 otherwise
assert (1 <= k < n and pick == c[k]) or (k == n and pick == 0)
min_ck = int(ceil(-pick / r))
max_ck = int(floor((z - pick) / r))
if k == 1:
for ck in range(max(min_ck, 0), min(max_ck, z) + 1):
c[0] = ck
yield c
else:
for ck in range(min_ck, max_ck + 1):
c[k - 1] = ck
for soln in iter_solutions_bounded(k - 1, ck):
yield soln
return iter_solutions_bounded(n, 0)
これを、参照するすべてのコードを削除し、得られたソリューションの数を合計するだけで、ソリューションをカウントするだけのコードに変換できますc
。最後に、メモ化によってパフォーマンスを向上させることができます。
from math import floor, ceil
def memoize(f):
cache = {}
def g(*args):
if args in cache:
return cache[args]
tmp = cache[args] = f(*args)
return tmp
return g
def len_range(a, b):
if a <= b:
return b - a
return 0
def count_solutions(r, n, z):
@memoize
def count_solutions_bounded(k, pick):
min_ck = int(ceil(-pick / r))
max_ck = int(floor((z - pick) / r))
if k == 1:
return len_range(max(min_ck, 0), min(max_ck, z) + 1)
else:
return sum(count_solutions_bounded(k - 1, ck) for ck in range(min_ck, max_ck + 1))
return count_solutions_bounded(n, 0)
考えられる改善点:
c 1 ... c nが常に ≤ zであることが真である場合、それを検出してすぐに 0 を返すことは、大きなnに対して非常に役立ちます。実際、実行時間を超高速の O( nz ) に短縮します。
c 1 ... c nがすべて負でないことが意図されている場合は、さらに優れています。min_ck
andに適切な変更を加えるとmax_ck
、この O( nz ) はより小さな定数で作成され、キャッシュは、私が取得した低速のハッシュ テーブルではなく、フラットな 2D 配列になる可能性があります。
このメモ化コードのように「オンデマンド」でキャッシュを作成するのではなく、体系的にキャッシュを構築することで、より良い結果が得られる可能性があります。最初に n=1 のキャッシュ全体を構築し、次に n=2 のキャッシュを構築します。そうすれば、再帰を回避でき、各ステップで不要になったキャッシュ データを破棄できます (n=2 の結果を計算した後、n=1 のエントリはもう必要ありません)。