0x00 问题引入
初中招生采取电脑随机派位方式进行录取,先由第一位家长代表抽取一位学生的报名序号作为录取第一人,再由第二位家长代表抽取每位被录取学生报名序号的间隔数,现某中学报名人数为885人,报名序号为: 0620001-0620885计划录取267人,现第一位家长代表抽取的号码为0620278第二位家长选取的间隔数字为865,请编写程序把所有被录取学生的报名序号输出至文件中。
格式如下:
0620278
0620258
0620239
0620221
0620204
0620188
0620173
…
0620684
0620152
0620503
0x01 问题分析
通过对问题进行分析,可以大致判断该题为不重复的相同间隔取数问题
原题中的数据量较为庞大,在进行问题分析时,可对问题进行简化
现有
1,2,3,...,19,20
20个数,要求从2
开始取数,每隔4
个取一次,共需要取10
个数,请输出取出的10个数
由于需要不重复进行取数,因此每次取完数后,需要从原序列中删除该数,并继续取数。在遍历到序列末尾时,下一次将从角标0
继续取数,因此循环变量需要对当前序列的长度进行求模运算,确保每次取到的数都在序列内,不会发生越界错误
用列表进行演算,前六次的结果如下表所示,取出的数分别为2、6、10、14、18、3
0x02 算法实现
alist = [i for i in range(1,886)]
index = 277 # 当前录取角标
for i in range(267):
print('0620%03d'%alist[index])
alist.pop(index)
index = (index + 865 - 1) % len(alist)
运行结果如下图所示
完整代码如下
fp = open('result.txt', 'a+')
alist = [i for i in range(1,886)]
index = 277 # 当前录取角标
for i in range(267):
fp.write('0620%03d\n'%alist[index])
alist.pop(index)
index = (index + 865 - 1) % len(alist)
fp.flush()
fp.close()
输出的文件如下图
0x03 思路拓展
上文提到的算法使用列表存储可选序号的范围,而对列表进行pop
操作将耗费更多资源,在对上文算法进行分析后,不难发现pop
元素的意义在于删除(跳过)已经选取的数。而如果仅使用循环,并对已经选取的数进行存储,依然可以达到跳过已经选取的数的目的
再次对0x01
中提到的简化问题进行分析,第1-10
步的选取过程如下图
每次选取仅为循环过程,前5
步没有任何问题,从第6
步开始,也就是从19
开始往后数4
个间隔,结果为2
,但是由于2
已经在之前取到过了,因此,第4
个间隔步骤并不能算有效步骤,需要往后再次寻找,直到找到一个没有被选过的数3
,因此第6
步的结果为3
依照这个思路,可以将算法改写如下
alist = []
i, now = 0, 278
while i < 267:
alist.append(now)
i += 1
# 后推
j = 0
while j < 865:
now += 1
if now in alist:
j -= 1
if now > 885:
now = 1
j += 1
print(alist)
运行后得到的数据与0x02
中得到的数据一致,如下图
0x04 总结
该问题为一个典型的不重复的相同间隔取数问题,该问题可以通过列表实现,但由于每次成功取数后对列表删除元素需要耗费更多资源,因此可以对算法进行优化,仅使用循环并存储已经取过的数,在进行循环取数的过程中,若经过已经取过的数,则该次循环为无效循环,需要再次向后寻找直到找到未被取过的数作为有效取数