很难准确说出您要问什么,但我认为您只是想在矩阵中的某个位置随机分配数字 3、4 和 5,条件是 i 不能被 3 和 j 整除不能被7 整除。如果这就是你想要的,那么应该这样做:
import random
array = [ [ 0 for j in range(28) ] for i in range(9) ]
for n in [3, 4, 5]:
while True:
i = random.randint(1, 9)
if i % 3 != 0:
break
while True:
j = random.randint(1, 28)
if j % 7 != 0:
break
array[i][j] = n
print('\n'.join(str(a) for a in array))
那么让我们来谈谈如何得到这个答案。首先,我们想要一种生成随机数的方法直到条件发生。在大多数语言中,这将涉及 do-while 循环,但 Python 没有这些。但是,我们可以只使用 while 循环来制作与 do-while 循环等效的东西:
# This says to run this loop *forever*
while True:
# do something here
pass # This means "do nothing" in Python
if condition:
# This says if the previously mentioned condition
# is True then we will stop executing the
# currently containing loop.
break
所以我刚刚展示的这个结构是一个构建块,您可以使用它来创建一个循环,该循环一直运行直到满足条件。
让我们看看它如何适合您的原始示例。我们想要一个在 [1, 9) 范围内不能被 3 整除的随机数。random.randint 函数将提供该范围内的随机数,但它不 保证它不能被 3 整除。所以我们需要自己强制执行该约束。一种方法是在不满足约束时简单地生成一个新数字。
所以现在我们可以使用前面讨论的循环构造来构建一个循环,该循环一直运行到我们有一个不能被 3 整除的数字:
while True:
i = random.randint(1, 9)
if i % 3 != 0:
break
我相信您可以在前一个循环中进行逻辑替换,看看它如何适合另一个示例。
所以现在我们可以多谈谈您在原始代码中哪里出了问题,以及如何防止以后再犯同样的错误。
我们先说这一行:
while 3 not in array:
首先,在开发代码时,最好在 Python 交互式解释器提示符下进行尝试。当您第一次尝试该语言的某个功能时,这一点尤其更是如此。正如我在评论中所展示的,while 循环中的条件是always False。如果您在解释器中尝试一下,原因就很清楚了:
>>> 3 in [[3],[3],[3]]
False
in 运算符仅在列表中查看一层。此外,在交互式测试时从小处着手总是一个好主意。请注意,我使用的是一个只有 3 个元素的列表,其中每个嵌套列表仅包含 1 个元素,而不是原始示例中的 9 元素列表,其中嵌套列表包含 28 个元素。
现在,我们可以用来使循环条件随时间变化的另一种方法是创建 in 运算符的“递归”版本。或者,我们可以硬编码它以期望包含列表的列表。我将采用第二种方法,因为它更简单,不知道您是否已经熟悉递归,这已经是一个很长的解释了。
def contains2d(outer, element):
"""Expects a 2D-array-like list. Returns True if any of the inner
sub-lists within the outer list contain element.
"""
return any([e in inner for inner in outer])
如果我们尝试这个函数,我们会发现它的行为与您最初期望的 in 运算符的行为一样:
>>> contains2d([[3],[3],[3]], 3)
True
>>> contains2d([[0],[0],[3]], 3)
True
>>> contains2d([[0],[0],[0]], 3)
False
那么现在让我们谈谈您对continue 关键字的误解。 while 循环自动重复。您不需要在 while 循环内使用 continue 来重复它。 continue 关键字仅用于跳过循环体的其余部分。如果您有一些不想进行正常循环处理的特殊情况,通常会使用此方法。下面是一个合理的例子,说明如何使用continue:
>>> for i in range(10):
... if i % 3 == 0:
... continue
... print(i)
...
1
2
4
5
7
8
请注意,将continue 放在循环末尾确实没有任何意义:
while True:
print("This loop runs forever!")
# The following continue is useless
continue
好的,既然我们有了contains2d,我们就可以开始考虑我们想要什么了。在您的示例中,您说您希望 array 变量在循环结束时包含 3,4 和 5。再一次,让我们从小事做起,看看在所需的情况下条件是否为真。我们从前面知道contains2d([[0],[0],[3]], 3) == True,所以这是一个不充分的循环终止标准。请记住,我们希望循环仅在满足所有条件时才为真。这意味着我们需要使用and 运算符
>>> contains2d([[0],[0],[3]], 3) and contains2d([[0],[0],[3]], 4) and contains2d([[0],[0],[3]], 5)
False
>>> contains2d([[4],[0],[3]], 3) and contains2d([[4],[0],[3]], 4) and contains2d([[4],[0],[3]], 5)
False
>>> contains2d([[4],[5],[3]], 3) and contains2d([[4],[5],[3]], 4) and contains2d([[4],[5],[3]], 5)
True
请注意,这种情况非常丑陋且难以编写。理想情况下,我们可能想重构它。一种方法是使用内置的all 函数。我会让你自己试验一下,但最终结果如下:
>>> all([contains2d([[4],[5],[3]], e) for e in [3,4,5]])
True
这要短得多,也更清楚。所以,继续前进,只要条件是 not True:
,我们就想运行这个循环
while not all([contains2d(array, e) for e in [3,4,5]]):
# ...
接下来的两行其实还好,不过我会按照PEP-8来格式化:
while not all([contains2d(array, e) for e in [3,4,5]]):
i = random.randint(1, 9)
j = random.randint(1, 28)
# ...
如果我们用 contains2d 函数替换 in 条件,那么我们几乎可以找到一个可行的解决方案:
while not all([contains2d(array, e) for e in [3,4,5]]):
i = random.randint(1, 9)
j = random.randint(1, 28)
if i % 3 != 0 and j % 7 != 0:
if contains2d(array, 5):
# ...
elif contains2d(array, 3):
# ...
else:
# ...
if 条件内的分配也非常好。但是,当您在循环中有 return 时,它将退出循环并退出整个包含函数。在此代码中,您实际上没有包含函数,因此这只会结束您的程序。这不是你想在这里做的,所以让我们放弃所有这些return 声明:
while not all([contains2d(array, e) for e in [3,4,5]]):
i = random.randint(1, 9)
j = random.randint(1, 28)
if i % 3 != 0 and j % 7 != 0:
if contains2d(array, 5):
array[i][j] = 3
elif contains2d(array, 3):
array[i][j] = 4
else:
array[i][j] = 5
这个程序非常接近是正确的。然而,最里面的if 块使用了一些奇怪的逻辑。如果你使用我的初始化和输出运行这个程序,你会看到这样的:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4]
[0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 5, 4]
[0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4]
[0, 4, 4, 4, 4, 4, 4, 0, 3, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4]
实际上我花了一分钟才弄清楚出了什么问题,但关键的观察是,第一次通过循环获得一对成功的 i 和 j,你会采取else 分支在嵌套列表中随机放置一个 5。下一次成功进入if 时,您将采用第一个分支,该分支在嵌套列表中随机放置一个 3。然而,事情就是这样横向发展的。在下一次成功的迭代中,您仍将采用第一个分支,因此您将随机放置 another 3 到嵌套列表中。您将继续这样做多次迭代。最终,您将覆盖先前写入的 5。此时,第一个条件将不再成立,而是您将开始采用第二个分支,该分支随机将 4 放入嵌套列表中。由于嵌套列表中不再存在 5,因此将继续采用此分支。最终,您将在嵌套列表中用 4s 覆盖所有先前写入的 3s。此时,第二个分支条件将不再成立,您将再次登陆 else 分支,将 5 写入嵌套列表。最后,第一个条件将再次成立,所以在下一次成功的迭代中,你将再次写一个 3,只要你不走运,那将错过 5,你现在已经满足终止条件对于外部 while 循环,您会得到一堆 4,只有一个 3 和一个 5。
也许这就是您想要做的,但从您的问题看来并非如此,所以我们假设您真的只想要嵌套列表中存在的每个数字之一。如果是这样的话,我们可以很容易地纠正之前的程序。我们只需要修复每个if 案例的条件。我们只想在 5 出现时采用第一个分支,但 3 不 出现。因此,这给了我们if contains2d(array, 5) and not contains2d(array, 3):。此外,如果 5 和 3 都已经存在,我们只想采用第二个分支。因此,这给了我们elif contains2d(array, 5) and contains2d(array, 3):。最后,如果不存在 5,我们只想采用最后一个分支。因此,我们必须将else 更改为另一个elif,从而为我们提供elif not contains2d(array, 5):。把这一切放在一起给我们:
array = [ [ 0 for j in range(28) ] for i in range(9) ]
while not all([contains2d(array, e) for e in [3,4,5]]):
i = random.randint(1, 9)
j = random.randint(1, 28)
if i % 3 != 0 and j % 7 != 0:
if contains2d(array, 5) and not contains2d(array, 3):
array[i][j] = 3
elif contains2d(array, 5) and contains2d(array, 3):
array[i][j] = 4
elif not contains2d(array, 5):
array[i][j] = 5
print('\n'.join(str(a) for a in array))
这实际上就像我原来的答案一样。但是,它并不是很令人满意,因为if 块内的逻辑相当复杂。实际上,必须发生一系列非常严格的事件。每当您想到一系列事物时,您都应该想到一个列表。在这种情况下,顺序是这样的,我们分配 5,然后我们分配 3,最后我们分配 4。这可以表示为这个列表:[5, 3, 4]。如果我们重新排序一下,我们可以得到以下程序:
array = [ [ 0 for j in range(28) ] for i in range(9) ]
for n in [5, 3, 4]:
while not contains2d(array, n):
i = random.randint(1, 9)
j = random.randint(1, 28)
if i % 3 != 0 and j % 7 != 0:
array[i][j] = n
print('\n'.join(str(a) for a in array))
这个特定的程序确实有一个缺陷,即后面的值之一可能会覆盖前面的值之一,因此如果你的随机数碰巧发生冲突,所有出现的数字的后置条件可能不成立。事实上,我最初的回答也有同样的问题。我将把它作为一个练习留给你来解决。