以一个基础题目:PAT (Basic Level) Practice 1005:继续3n+1,来展现这个问题。
题目的具体描述见PAT网站。
1.写入输入的数据后,把要验证的数列转化为数字元素的列表num_list。
2.从头开始遍历num_list,对每个元素num,研究3n+1猜想,而更新num,若更新后的num in num_list,则从num_list中remove它。
3.num_list中只剩下了"关键数",由内置函数sorted排序(reverse=True)。
4.由for遍历sorted_num_list[:-1],print(num, end=" “)。再print(sorted_num_list[-1], end=”")。
解题代码length = int(input())
s = input()
num_list = [int(n) for n in s.split(" ")]
for num in num_list:
while num != 1:
if num % 2 == 0:
num = num // 2
else:
num = (3 * num + 1) // 2
if num in num_list:
num_list.remove(num)
sorted_num_list = sorted(num_list, reverse=True)
for n in sorted_num_list[:-1]:
print(n, end=" ")
print(sorted_num_list[-1], end="")
代码测试
输入:
6 3 5 6 7 8 11
输出:
11 7 6
然而正确的输出是:“7 6”。
出现了什么问题?按理说,11被7覆盖,在遍历7的时候会被删除。然而并没有。
通过debug研究,发现与for的遍历机制有关–for通过seq的索引i,从小到大(i=0,1,2,…,len(seq)-1)遍历seq:
第一个(i=0)遍历的元素是3,则5,8被从seq中删去,seq变为"3,6,7,11"。
下一个(i=1)遍历的是6,则3被从seq中删去,seq变为"6, 7, 11"。
则下一个(i=2)遍历的是11,则没有元素被删除,seq不变。
而len(seq)-1已经是2,则不再遍历,因此seq不会遍历7,因而11被保留。这样的代码不正确。
另一方面,官方文档中甚至对这个问题有相关建议:
特别地!!我们要注意到图中for遍历copy()那里!!
被遍历的copy并没有因原序列变化而变化,一直是最初的样子–说明程序执行时.copy()函数只执行了最初那一次!然后返回的变量一直用!
但是如果不是遍历seq.copy()而遍历seq,debug测试发现for遍历的序列就是变化的了。
也就是说,for遍历的序列,每访问一个元素,都要读取一次被遍历的序列。若被遍历的是一个函数返回值,则函数只执行最初那一次。
如何解决for带来的问题,并给出上述实例的代码题解?官方文档已经给出提示:创建另一个序列来辅助。
另外在本题中,我们要做到:若是已被覆盖的数,则不应研究它的"3n+1"。
我们可以用另外一个序列covered_list存已被覆盖的数,
当每访问一个待求证数,则先判断是否in covered_list,决定是否研究它的"3n+1"。
在研究某个数的"3n+1"过程中,将发现的被覆盖的数加入covered_list。
最后排序[n for n in num_list if n not in covered_list]。
代码:
length = int(input())
s = input()
num_list = [int(n) for n in s.split(" ")]
# 新一个list:covered_list来保存已经被“覆盖”的数。如果num not in covered_list,则研究猜想。
covered_list = []
for num in num_list:
if num not in covered_list:
while num != 1:
if num % 2 == 0:
num = num // 2
else:
num = (3 * num + 1) // 2
if num in num_list:
covered_list.append(num)
sorted_num_list = sorted([num for num in num_list if num not in covered_list], reverse=True)
for n in sorted_num_list[:-1]:
print(n, end=" ")
print(sorted_num_list[-1], end="")
意外发现的问题:.copy()与for循环之间另藏的玄机?
我现在还并不明白这是怎么回事–
下面这个代码能够得满分,但并没有成功避开所有被"覆盖"的数,似乎只有首元素被"覆盖"的数避开了研究"3n+1"。
length = int(input())
s = input()
num_list = [int(n) for n in s.split(" ")]
for num in num_list:
print(num_list)
print(num)
while num != 1:
if num % 2 == 0:
num = num // 2
else:
num = (3 * num + 1) // 2
if num in num_list:
num_list.remove(num)
num_list = num_list.copy()
sorted_num_list = sorted(num_list, reverse=True)
for n in sorted_num_list[:-1]:
print(n, end=" ")
print(sorted_num_list[-1], end="")
输出(11本被7"覆盖",应该先被remove了,然而仍被访问研究了"3n+1"):
[3, 5, 6, 7, 8, 11] 3 [3, 6, 7, 11] 6 [6, 7, 11] 7 [6, 7] 11 7 6
.copy()明明每次都执行了,然后for只取了原num_list,和第一次的.copy():[3, 6, 7, 11]。
for除了第一次访问序列访问的是原num_list,其他时候都是第一次的copy()。



