我正在尝试使用给定的x,y和z查找可能的排列总数。 x是初始数字,y是最终数字,z是按下的按钮总数。 我应该只是像国际象棋中的骑士一样从一个数字到一个数字,在一个' L'形状。 例如,如果您刚刚拨打1,则您拨打的下一个号码必须是6或8.如果您刚刚拨打了6,则下一个号码必须是1或7。 目前,我的实现输出了我给出的所有数字的正确答案。然而,由于计算时间是指数的,所以上帝可怕的慢。我想知道的是我如何在线性时间内或多或少地计算出来。 z将始终介于1和100之间。
##Computes the number of phone numbers one
##can dial that start with the digit x, end
##in the digit y, and consist of z digits in
##total. Output this number as a
##string representing the number in base-10.
##Must follow "knights rule" moves, like chess
##########_________##########
##########|1||2||3|##########
##########|_||_||_|##########
##########|4||5||6|##########
##########|_||_||_|##########
##########|7||8||9|##########
##########|_||_||_|##########
##########|_||0||_|##########
##########^^^^^^^^^##########
dict = {0: [4, 6], 1: [6, 8], 2: [7, 9], 3: [4, 8],
4: [0, 3, 9], 5: [], 6: [0, 1, 7], 7: [2, 6], 8: [1, 3],
9: [2, 4]}
def recAnswer(current, y, z, count, total):
if count == z and current == y:
total += 1
return total
count+=1
if count > z:
return total
for i in dict.get(current):
total = recAnswer(i, y, z, count, total)
return total
def answer(x, y, z):
if x == y:
if z%2 == 0:
return '0'
elif x == 5 or y == 5:
if z == 1 and x == y:
return '1'
else:
return '0'
elif x%2 != 0 and y%2 == 0:
if z%2 != 0:
return '0'
elif x%2 == 0 and y%2 != 0:
if z%2 != 0:
return '0'
elif x%2 == 0 and y%2 ==0:
if z%2 == 0:
return '0'
elif x%2 != 0 and y%2 != 0:
if z%2 == 0:
return '0'
total = recAnswer(x,y,z,1,0)
return str(total)
def test():
for i in xrange(1,15,1):
print i,":",answer(1,3,i)
print answer(6, 2, 5)
print answer(1, 6, 3)
print answer(1, 1, 99)
test()
答案 0 :(得分:4)
您的代码速度慢的原因是您最终反复访问(并重新计算)相同的组合。您可以使用称为memoization的技术缩短重新计算部分。
记忆很容易添加,但让我们重新设计你的递归函数,以便调用函数进行累积,而不是函数本身。换句话说,不要传递总数并仅返回此子路径的组合:
def recAnswer(current, y, z, count):
if count == z and current == y:
return 1
count += 1
if count > z:
return 0
total = 0
for i in dict.get(current):
total += recAnswer(memo, i, y, z, count)
return total
此更改不会更改计算本身;结果仍然相同。
现在让我们将所有重复的调用切断为相同的参数。我们将字典memo
传递给函数。这个dict的关键是你的函数参数的元组。作为递归函数的第一步,检查计算是否已完成。作为初始计算的最后一步,将解决方案添加到dict:
def recAnswer(memo, current, y, z, count):
# dict key is the tuple of arguments
key = (current, y, z, count)
# Have we been here before? If so, return memoized answer
if key in memo:
return memo[key]
if count == z and current == y:
return 1
count += 1
if count > z:
return 0
total = 0
for i in dict.get(current):
total += recAnswer(memo, i, y, z, count)
# Store answer for later use
memo[key] = total
return total
然后用空字典进行计算:
total = recAnswer({}, x, y, z, 1)
附录:现在我已经了解了@decorator
s,我将使用memoization来装饰该函数,以便不更改原始函数。好吧,我将再做一次更改,正如Janne在评论中提到的那样:我将目标cound和当前计数合并为一个变量,计数从目标值开始并倒数到零而是达到目标值。
首先,memoization装饰器,它将是一个包含装饰函数的类,func
和memoization字典。该类必须使用所需数量的参数实现功能__call__
:
class memoized(object):
"""Decorator function that adds the memoization"""
def __init__(self, func):
self.func = func
self.memo = {}
def __call__(self, current, target, count):
key = (current, target, count)
if key not in self.memo:
self.memo[key] = self.func(current, target, count)
return self.memo[key]
现在是在def
inition之前使用装饰器的简化函数:
@memoized
def recAnswer(current, target, count):
"""Unmemoized original function"""
if count == 0:
return int(current == target) # False: 0, True: 1
total = 0
for next in dict[current]:
total += recAnswer(next, target, count - 1)
return total
@memoized
装饰器通过recAnswer
调用函数memoized.__call__
来处理memoization。像这样调用递归函数:
total = recAnswer(x, y, z - 1)
(这里的-1考虑到在原始代码中,计数从1开始。)
可能还有改进的余地。例如,您可以使用splat语法为memoized
装饰器类变量创建参数数量,以便您可以将memoizer重新用于其他函数:
def __call__(self, *args):
if args not in self.memo:
self.memo[args] = self.func(*args)
return self.memo[args]
所有这一切的结果是,如果你有一个问题,你一次又一次地重新评估同一组参数,只需跟踪以前计算的结果可以给你一个巨大的加速,而不必摆弄基本实施。
答案 1 :(得分:0)
"""Solve the phone/chess paths problem from this question:
https://codereview.stackexchange.com/questions/71988
"""
# Move dictionary
MOVES = {0: [4, 6],
1: [6, 8],
2: [7, 9],
3: [4, 8],
4: [0, 3, 9],
5: [],
6: [0, 1, 7],
7: [2, 6],
8: [1, 3],
9: [2, 4]}
# Cache implementation
def cache(func):
"""Standard cache decorator implementation."""
cache_dct = {}
def wrapper(*args):
if args not in cache_dct:
cache_dct[args] = func(*args)
return cache_dct[args]
return wrapper
# Recusive function
@cache
def rec(x, y, z):
"""Recursively count the number of path
to go from x to y in z moves.
"""
if not z:
return int(x == y)
return sum(rec(x, y, z-1) for x in MOVES[x])
# Paths function
def paths(x, y, z):
"""Count the number of paths to go from x to y
with z key strokes.
"""
if not z:
return 0
return rec(x, y, z-1)
# Main execution
if __name__ == "__main__":
example = "paths(1, 1, 99)"
print example + " = " + str(eval(example))
# Output: paths(1, 1, 99) = 30810672576979228350316940764381184