我想编写一个函数,该函数使用以下规则集从标准电话键盘生成所有可能的号码(图1):
在国际象棋中,骑士(有时称为马)会垂直移动两步,水平移动一步,或者水平移动两步,垂直移动一步。
电话号码中只能使用数字-即不允许使用(#)和(*)键。
该功能必须以电话号码的长度和初始位置作为输入,而输出则给出唯一的电话号码。
我是一个新手,在构建逻辑上面临困难。 我尝试按照以下步骤进行操作,这绝对不是正确的方法。
def genNumbers(len, initpos):
numb = list('2xxxxxxxxx')
#index 1
numb[1] = 7 or 9
if numb[1] == 7:
numb[2] == 2 or 6
elif numb[1] == 9:
numb[2] == 2 or 4
#index 2
if numb[2]== 2:
numb[3] == 7 or 9
elif numb[2]== 4:
numb[3] == 3 or 9
elif numb[2]== 6:
numb[3] == 1 or 7
#index 3
if numb[3]== 1:
numb[4] == 6 or 8
elif numb[3]== 3:
numb[4] == 4 or 8
elif numb[3]== 7:
numb[4] == 2 or 6
elif numb[3]== 9:
numb[4] == 2 or 4
#index 4
if numb[4] == 8:
numb[5]== 1 or 3
elif numb[4] == 2:
numb[5]== 7 or 9
elif numb[4] == 4:
numb[5]== 3 or 9
elif numb[4] == 6:
numb[5]== 1 or 7
#index 5
if numb[5] == 1:
numb[6]== 6 or 8
elif numb[5] == 3:
numb[6]== 4 or 8
elif numb[5] == 7:
numb[6]== 2 or 6
elif numb[5] == 9:
numb[6]== 2 or 4
#index 6
if numb[6] == 2:
numb[7]== 7 or 9
elif numb[6] == 4:
numb[7]== 3 or 9
elif numb[6] == 6:
numb[7]== 1 or 7
elif numb[6] == 8:
numb[7]== 1 or 3
#index 7
if numb[7] == 1:
numb[8]== 6 or 8
elif numb[7] == 3:
numb[8]== 4 or 8
elif numb[7] == 7:
numb[8]== 2 or 6
elif numb[7] == 9:
numb[8]== 2 or 4
#index 8
if numb[8] == 6:
numb[9]== 1 or 7
elif numb[8] == 8:
numb[9]== 1 or 3
elif numb[8] == 4:
numb[9]== 3 or 9
elif numb[8] == 2:
numb[9]== 7 or 9
return numb
任何帮助将不胜感激!
答案 0 :(得分:3)
该功能必须将电话号码的长度和初始位置作为输入,而输出则给出唯一电话号码的数量。
您的问题可以通过图论和线性代数(这些学科相遇的一个有趣的地方是离散数学)来解决。
首先,我们创建一个Adjacency Matrix来表示电话键盘上的合法动作:
import numpy as np
A = np.zeros((10, 10))
A[0,4]=1
A[0,6]=1
A[1,6]=1
A[1,8]=1
A[2,7]=1
A[2,9]=1
A[3,4]=1
A[3,8]=1
A[4,0]=1
A[4,3]=1
A[4,9]=1
A[6,0]=1
A[6,1]=1
A[6,7]=1
A[7,2]=1
A[7,6]=1
A[8,1]=1
A[8,3]=1
A[9,2]=1
A[9,4]=1
我们可以检查矩阵是否对称(不是必需的,但这是系统的属性):
np.allclose(A, A.T) # True
邻接矩阵的条目如下:A[0,4]=1
表示从顶点0
到顶点4
的移动,而A[0,5]=0
表示从{{ 1}}到0
。
5
然后我们计算[[0. 0. 0. 0. 1. 0. 1. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 1. 0. 1. 0.]
[0. 0. 0. 0. 0. 0. 0. 1. 0. 1.]
[0. 0. 0. 0. 1. 0. 0. 0. 1. 0.]
[1. 0. 0. 1. 0. 0. 0. 0. 0. 1.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 1. 0. 0. 0. 0. 0. 1. 0. 0.]
[0. 0. 1. 0. 0. 0. 1. 0. 0. 0.]
[0. 1. 0. 1. 0. 0. 0. 0. 0. 0.]
[0. 0. 1. 0. 1. 0. 0. 0. 0. 0.]]
的幂A
的乘积,这得出了长度为9
的{{3}}个数字(这对应于长度为唯一的电话号码的计数在两个给定顶点之间的9
(以数字10
开始,以数字x
结束):
y
路径长度为W = np.linalg.matrix_power(A, 9)
,因为顶点是数字,边缘在键盘上是移动,因此要拨n-1
位电话号码,您需要10
个移动(长度为{{ 1}})。
它给我们:
9
矩阵9
的条目读为:[[ 0. 0. 336. 0. 544. 0. 544. 0. 336. 0.]
[ 0. 0. 264. 0. 432. 0. 448. 0. 280. 0.]
[336. 264. 0. 264. 0. 0. 0. 280. 0. 280.]
[ 0. 0. 264. 0. 448. 0. 432. 0. 280. 0.]
[544. 432. 0. 448. 0. 0. 0. 432. 0. 448.]
[ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[544. 448. 0. 432. 0. 0. 0. 448. 0. 432.]
[ 0. 0. 280. 0. 432. 0. 448. 0. 264. 0.]
[336. 280. 0. 280. 0. 0. 0. 264. 0. 264.]
[ 0. 0. 280. 0. 448. 0. 432. 0. 264. 0.]]
表示W
个电话号码,长度为W[2,1] = 264
,以264
开头,以10
结尾
现在,我们总结从顶点2
开始的步行:
1
您提供的一组规则中有2
个电话,长度为np.sum(W[2,:]) # 1424.0
,以数字1424
开头。
该函数编写起来很简单:
10
工作的大部分内容是对矩阵进行编码,该矩阵描述了规则集(允许在键盘上移动)。
基于观察到的问题,我们可以从问题描述中得出,例如@SpghttCd,我们可以检查是否从2
开始没有包含数字def phoneNumbersCount(n=10, i=2, A=A):
return np.sum(np.linalg.matrix_power(A, n-1)[i,:])
的长度10
:
2
我们可以检查是否从5
开始不能写入长度为W[2,5] # 0.0
的数字:
10
对于给定的规则,实际上数字5
完全不可用。
我们还可以检查另一个不明显的属性,例如:不存在长度为phoneNumbersCount(10, 5) # 0.0
并以5
,10
结尾的长度2
,2
,4
或5
:
6
由于图没有定向(每个边都在两个方向上都存在),因此邻接矩阵是对称的。因此,矩阵创建可以简化为:
8
一些有用的参考资料,以了解其工作方式和原因:
答案 1 :(得分:2)
让我们提出另一种解决问题的方法,该方法不涉及线性代数,但仍然依赖于图论。
问题的自然表示是一个如下图所示的图形:
等于:
我们可以用字典来表示该图:
G = {
0: [4, 6],
1: [6, 8],
2: [7, 9],
3: [4, 8],
4: [0, 3, 9],
5: [], # This vertex could be ignored because there is no edge linked to it
6: [0, 1, 7],
7: [2, 6],
8: [1, 3],
9: [2, 4],
}
这种结构将使您无需编写if
语句。
上面的表示包含与邻接矩阵相同的信息。此外,我们可以从上面的结构(将布尔稀疏矩阵转换为积分矩阵)生成它:
def AdjacencyMatrix(d):
A = np.zeros([len(d)]*2)
for i in d:
for j in d[i]:
A[i,j] = 1
return A
C = AdjacencyMatrix(G)
np.allclose(A, C) # True
A
是another answer中定义的邻接矩阵。
现在我们可以使用递归生成所有电话号码:
def phoneNumbers(n=10, i=2, G=G, number='', store=None):
if store is None:
store = list()
number += str(i)
if n > 1:
for j in G[i]:
phoneNumbers(n=n-1, i=j, G=G, number=number, store=store)
else:
store.append(number)
return store
然后我们建立电话号码列表:
plist = phoneNumbers(n=10, i=2)
它返回:
['2727272727',
'2727272729',
'2727272760',
'2727272761',
'2727272767',
...
'2949494927',
'2949494929',
'2949494940',
'2949494943',
'2949494949']
现在只需要考虑列表的长度:
len(plist) # 1424
我们可以检查是否有重复项:
len(set(plist)) # 1424
我们可以检查比我们对另一个答案中的最后一位数字所做的观察在该版本中仍然适用的情况:
d = set([int(n[-1]) for n in plist]) # {0, 1, 3, 7, 9}
电话号码不能以:
结尾set(range(10)) - d # {2, 4, 5, 6, 8}
第二个答案:
numpy
(不需要线性代数),它仅使用Python标准库; x********y
的形式获取有关电话号码的详细信息; 递归函数的复杂度应限制在O(2^n)
和O(3^n)
之间,因为递归树的深度为n-1
(所有分支的深度相同),并且每个内部节点都创建最少2个边缘,最多3个边缘。这里的方法不是分而治之算法,而是 Combinatorics 字符串生成器,这就是我们期望复杂度呈指数级的原因。
对两个函数进行基准测试似乎可以验证这一说法:
递归函数以对数刻度显示线性行为,该行为证实了指数复杂度并受其限制。更糟糕的是,除了计算之外,将需要越来越多的内存来存储列表。除了n=23
我什么都做不到,然后笔记本电脑在冻结MemoryError
之前就冻结了。对复杂度的更好估计是O((20/9)^n)
,其中基数等于mean of vertices degrees(断开的顶点将被忽略)。
相对于问题大小n
,矩阵乘方方法似乎具有恒定的时间。 numpy.linalg.matrix_power
文档中没有实现细节,但这是known eigenvalues problem。因此,我们可以解释为什么复杂度在n
之前似乎是恒定的。这是因为矩阵形状独立于n
(它仍然是10x10
矩阵)。大部分的计算时间专用于解决特征值问题,而不是将对角特征矩阵提高到第n次幂,这是一个微不足道的运算(也是唯一的依赖项)到n
)。这就是为什么该解决方案以“恒定时间”执行的原因。此外,它还需要准恒定数量的内存来存储Matrix及其分解,但这也独立于n
。
在用于基准测试功能的代码下面找到:
import timeit
nr = 20
ns = 100
N = 15
nt = np.arange(N) + 1
t = np.full((N, 4), np.nan)
for (i, n) in enumerate(nt):
t[i,0] = np.mean(timeit.Timer("phoneNumbersCount(n=%d)" % n, setup="from __main__ import phoneNumbersCount").repeat(nr, number=ns))
t[i,1] = np.mean(timeit.Timer("len(phoneNumbers(n=%d, i=2))" % n, setup="from __main__ import phoneNumbers").repeat(nr, number=ns))
t[i,2] = np.mean(timeit.Timer("len(phoneNumbers(n=%d, i=0))" % n, setup="from __main__ import phoneNumbers").repeat(nr, number=ns))
t[i,3] = np.mean(timeit.Timer("len(phoneNumbers(n=%d, i=6))" % n, setup="from __main__ import phoneNumbers").repeat(nr, number=ns))
print(n, t[i,:])