Python 2.7
Cách tiếp cận này tận dụng các cân nhắc sau:
Bất kỳ số nguyên nào cũng có thể được biểu diễn dưới dạng tổng lũy thừa của hai. Số mũ trong lũy thừa của hai cũng có thể được biểu diễn dưới dạng lũy thừa của hai. Ví dụ:
8 = 2^3 = 2^(2^1 + 2^0) = 2^(2^(2^0) + 2^0)
Các biểu thức mà chúng tôi kết thúc có thể được biểu diễn dưới dạng tập hợp các tập hợp (trong Python, tôi đã sử dụng tích hợp sẵn frozenset
):
0
trở thành tập hợp trống {}
.
2^a
trở thành tập hợp chứa tập đại diện a
. Vd: 1 = 2^0 -> {{}}
và 2 = 2^(2^0) -> {{{}}}
.
a+b
trở thành sự kết hợp của các bộ đại diện a
và b
. Ví dụ,3 = 2^(2^0) + 2^0 -> {{{}},{}}
Nó chỉ ra rằng các biểu thức của biểu mẫu 2^2^...^2
có thể dễ dàng được chuyển đổi thành biểu diễn tập duy nhất của chúng, ngay cả khi giá trị số quá lớn để được lưu trữ dưới dạng một số nguyên.
Đối với n=20
, điều này chạy trong 8,7 giây trên CPython 2.7.5 trên máy của tôi (chậm hơn một chút trong Python 3 và chậm hơn nhiều trong 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()
(Khái niệm của trình trang trí ghi nhớ được sao chép từ http://code.activestate.com/recipes/578231-probossible-the-fastest-memoization-decorator-in-the-/ .)
Đầu ra:
8.667753234
0...........1
1...........1
2...........1
3...........2
4...........4
5...........8
6..........17
[...]
19.....688366
20....1619087
Thời gian cho khác nhau n
:
n time
16 0.240
17 0.592
18 1.426
19 3.559
20 8.668
21 21.402
Bất kỳ n
trên 21 kết quả trong một lỗi bộ nhớ trên máy của tôi.
Tôi sẽ quan tâm nếu ai đó có thể làm điều này nhanh hơn bằng cách dịch nó sang một ngôn ngữ khác.
Chỉnh sửa: Tối ưu hóa get_results
chức năng. Ngoài ra, sử dụng Python 2.7.5 thay vì 2.7.2 khiến nó chạy nhanh hơn một chút.
2^n
, và do đó sẽ không cần thiết phải theo dõi bất cứ điều gì ngoại trừn
. Tức là, chỉ sử dụng các quy tắc lũy thừa có vẻ khôn ngoan. Tuy nhiên, chắc chắn có một cách thông minh hơn và hoàn toàn đại số để làm điều này.