【问题标题】:generating phone numbers using specific set of rules in python使用 python 中的特定规则集生成电话号码
【发布时间】:2019-05-10 20:30:20
【问题描述】:

我想编写一个函数,使用以下规则集从标准电话键盘(图 1)生成所有可能的数字:

  • 电话号码以数字 2 开头
  • 电话号码长度为 10 位
  • 每个电话号码中的连续数字被选为国际象棋中的骑士移动

在国际象棋中,马(有时称为马)垂直移动两步,水平移动一步,或者水平移动两步,垂直移动一步。

电话号码中只能使用数字 - 即不允许使用 (#) 和 (*) 键。

该函数必须将电话号码的长度和初始位置作为输入,输出给出唯一电话号码的数量。

我是一个新手,很难构建逻辑。 我尝试按照以下方式进行操作,这绝对不是正确的方法。

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

任何帮助将不胜感激!

【问题讨论】:

  • 这不仅可能不是正确的方法,而且对计算机程序的工作原理缺乏一些基本的了解。您不能简单地为变量分配一个另一个值,希望计算机现在能够理解该变量不应具有除这两个之外的其他值...第二:分配是=,比较是==。除此之外,在所描述的问题上有两个问题:很好的逻辑挑战,但有点奇怪的是没有电话号码会包含5。并且:你应该尝试接触一个叫做recursive functions的概念
  • 这是一个很好的关于这个问题的文章,详细讨论了几个解决方案:hackernoon.com/…

标签: python function random generator


【解决方案1】:

函数签名

该函数必须将电话号码的长度和初始位置作为输入,并在输出时给出唯一电话号码的数量

关键思想

您的问题可以通过图论和线性代数来解决(这些学科相遇的一个有趣的地方是离散数学)。

首先我们创建一个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 移动到顶点4A[0,5]=0 表示没有从05 的移动。

[[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.]]

然后我们计算A9 的幂次计算得到两个给定顶点之间长度为9walks 的数量(这对应于长度为10 的唯一电话号码的数量) (以x开头,以y结尾):

W = np.linalg.matrix_power(A, 9)

路径长度为n-1,因为顶点是数字,边是键盘上的移动,因此拨打10-digits 电话号码需要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电话号码长度为102开头并以1结尾。

现在我们总结从顶点2开始的游走:

np.sum(W[2,:]) # 1424.0

对于您提供的规则集,有1424 电话号码长度为10 以数字2 开头。

功能

这个函数就很简单了:

def phoneNumbersCount(n=10, i=2, A=A):
    return np.sum(np.linalg.matrix_power(A, n-1)[i,:])

大部分工作包括对描述规则集的矩阵进行编码(允许在键盘上移动)。

检查

根据我们可以从问题描述中得出的观察结果,例如@SpghttCd 所做的,我们可以检查是否存在从2 开始的包含数字5 的长度10

W[2,5] # 0.0

我们可以检查从5开始的长度不能写成10

phoneNumbersCount(10, 5) # 0.0

实际上数字5对于给定的规则集根本不可用。

我们还可以检查其他不明显的属性,例如:没有长度数102开头并以2456结尾或8:

W[2,:] # [336. 264.   0. 264.   0.   0.   0. 280.   0. 280.]

提示

由于图没有方向(每条边都存在于两个方向),邻接矩阵是对称的。因此矩阵的创建可以简化为:

B = np.zeros((10, 10))
B[0,4]=1
B[0,6]=1
B[1,6]=1
B[1,8]=1
B[2,7]=1
B[2,9]=1
B[3,4]=1
B[3,8]=1
B[4,9]=1
B[6,7]=1
B = np.maximum(B, B.T)

参考文献

一些有用的参考资料可帮助您了解其运作方式和原因:

【讨论】:

  • 无法寻求更好的解释!非常感谢!
【解决方案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

其中Aanother 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形式的号码详细信息;
  • 使用递归来解决问题,并且似乎具有指数时间复杂度,如果我们不需要生成电话号码,我们应该使用 Matrix Power 版本

基准测试

递归函数的复杂度应在O(2^n)O(3^n) 之间,因为递归树的深度为n-1(并且所有分支具有相同的深度)并且每个内部节点创建最少2 条边,最多创建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)。这就是为什么该解决方案以“恒定时间”执行的原因。此外,它还需要一个准常数的内存来存储矩阵及其分解,但这也与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,:])

【讨论】:

  • 这个函数的时间复杂度是多少?你能解释一下吗?
  • @FJ_Abbasi,这是一个完全不同的问题。让我考虑一下。您可能还想在处理此类问题的社区mathoverflow.net 上提问。如果你这样做,交叉链接问题(使帖子相互指向)我很高兴看到关于这个新问题的另一个反馈。
  • @FJ_Abbasi,矩阵产品以O(n ^3) 为界。而且,我认为我的第一个客人是正确的:递归三的深度为n-1,并在每个内部节点处最多创建 3 条边,假设其他操作为O(1),则全局复杂性必须以O(3^n) 为界。不是分治算法,而是组合字符串生成器。随着n 的增长,后一种解决方案的效率会降低。
  • 我将把它发布在 mathoverflow.net 上。所以你认为它的时间复杂度是 O(3^n)?
  • 乍一看似乎很奇怪,性能很糟糕,但是如果你画递归树你会发现它最后大约是3^(n-1)节点。由专业数学家检查这一点会很有趣。很久以前我计算过时间复杂度,你会得到一个准确的答案。我将对这两个函数进行基准测试,看看是否会出现这种渐近行为。
猜你喜欢
  • 1970-01-01
  • 2018-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-11
  • 1970-01-01
  • 2019-02-06
相关资源
最近更新 更多