Python 2.7
这种方法利用了以下注意事项:
任何整数都可以表示为2的幂。2的幂的指数也可以表示为2的幂。例如:
8 = 2^3 = 2^(2^1 + 2^0) = 2^(2^(2^0) + 2^0)
我们最终得到的这些表达式可以表示为集合集(在Python中,我使用内置的frozenset
):
0
成为空集{}
。
2^a
成为包含代表的集合的集合a
。例如:1 = 2^0 -> {{}}
和2 = 2^(2^0) -> {{{}}}
。
a+b
成为表示a
和的集合的串联b
。例如,3 = 2^(2^0) + 2^0 -> {{{}},{}}
事实证明2^2^...^2
,即使数值太大而无法存储为整数,形式的表达式也可以轻松地转换为其唯一的集合表示形式。
对于n=20
,这在我的机器上的CPython 2.7.5上以8.7s运行(在Python 3中慢一点,在PyPy中慢得多):
"""Analyze the expressions given by parenthesizations of 2^2^...^2.
Set representation: s is a set of sets which represents an integer n. n is
given by the sum of all 2^m for the numbers m represented by the sets
contained in s. The empty set stands for the value 0. Each number has
exactly one set representation.
In Python, frozensets are used for set representation.
Definition in Python code:
def numeric_value(s):
n = sum(2**numeric_value(t) for t in s)
return n"""
import itertools
def single_arg_memoize(func):
"""Fast memoization decorator for a function taking a single argument.
The metadata of <func> is *not* preserved."""
class Cache(dict):
def __missing__(self, key):
self[key] = result = func(key)
return result
return Cache().__getitem__
def count_results(num_exponentiations):
"""Return the number of results given by parenthesizations of 2^2^...^2."""
return len(get_results(num_exponentiations))
@single_arg_memoize
def get_results(num_exponentiations):
"""Return a set of all results given by parenthesizations of 2^2^...^2.
<num_exponentiations> is the number of exponentiation operators in the
parenthesized expressions.
The result of each parenthesized expression is given as a set. The
expression evaluates to 2^(2^n), where n is the number represented by the
given set in set representation."""
# The result of the expression "2" (0 exponentiations) is represented by
# the empty set, since 2 = 2^(2^0).
if num_exponentiations == 0:
return {frozenset()}
# Split the expression 2^2^...^2 at each of the first half of
# exponentiation operators and parenthesize each side of the expession.
split_points = xrange(num_exponentiations)
splits = itertools.izip(split_points, reversed(split_points))
splits_half = ((left_part, right_part) for left_part, right_part in splits
if left_part <= right_part)
results = set()
results_add = results.add
for left_part, right_part in splits_half:
for left in get_results(left_part):
for right in get_results(right_part):
results_add(exponentiate(left, right))
results_add(exponentiate(right, left))
return results
def exponentiate(base, exponent):
"""Return the result of the exponentiation of <operands>.
<operands> is a tuple of <base> and <exponent>. The operators are each
given as the set representation of n, where 2^(2^n) is the value the
operator stands for.
The return value is the set representation of r, where 2^(2^r) is the
result of the exponentiation."""
# Where b is the number represented by <base>, e is the number represented
# by <exponent> and r is the number represented by the return value:
# 2^(2^r) = (2^(2^b)) ^ (2^(2^e))
# 2^(2^r) = 2^(2^b * 2^(2^e))
# 2^(2^r) = 2^(2^(b + 2^e))
# r = b + 2^e
# If <exponent> is not in <base>, insert it to arrive at the set with the
# value: b + 2^e. If <exponent> is already in <base>, take it out,
# increment e by 1 and repeat from the start to eventually arrive at:
# b - 2^e + 2^(e+1) =
# b + 2^e
while exponent in base:
base -= {exponent}
exponent = successor(exponent)
return base | {exponent}
@single_arg_memoize
def successor(value):
"""Return the successor of <value> in set representation."""
# Call exponentiate() with <value> as base and the empty set as exponent to
# get the set representing (n being the number represented by <value>):
# n + 2^0
# n + 1
return exponentiate(value, frozenset())
def main():
import timeit
print timeit.timeit(lambda: count_results(20), number=1)
for i in xrange(21):
print '{:.<2}..{:.>9}'.format(i, count_results(i))
if __name__ == '__main__':
main()
(备注装饰器的概念是从http://code.activestate.com/recipes/578231-probably-the-fastest-memoization-decorator-in-the-/复制的。)
输出:
8.667753234
0...........1
1...........1
2...........1
3...........2
4...........4
5...........8
6..........17
[...]
19.....688366
20....1619087
不同的时间n
:
n time
16 0.240
17 0.592
18 1.426
19 3.559
20 8.668
21 21.402
任何n
高于21的值都会在我的计算机上导致内存错误。
我很想知道是否有人可以通过将其翻译成另一种语言来使其更快。
编辑:优化get_results
功能。另外,使用Python 2.7.5而不是2.7.2使其运行更快。
2^n
,因此没有必要跟踪除之外的任何内容n
。即,仅使用求幂规则似乎是明智的。但是,肯定有一种更聪明,完全代数的方法可以做到这一点。