具有多个输出的分层 while 循环

tiered while loops with multiple outputs

我正在尝试学习数组。我知道 python 有列表,没有数组,但思路是一样的。我有一个像数组一样设置的列表列表,我正在尝试修改它们以获得随机艺术乐趣,但我只能从这段代码中得到一个结果随机数。

##prior list creation code making a large list of zeros called "array"###
while 3 not in array:
    i= random.randint(1,9)
    j= random.randint(1,28)
    if (i%3)!=0 and (j%7)!=0:
        if 5 in array:
            array[i][j]=3
            return array

        elif 3 in array:
            array[i][j]=4
            return array

        else:
            array[i][j]=5
            amy=(i, j)
            return array
    continue
##the resulting list called "array" does not chnange any zero to any number except one to "5"##

我已经删除了使数组充满零的代码。唯一会出现的数字是 5...理想情况下,我希望每个数字在每个 运行 中只出现一次,但在不同的位置

我做错了什么?我不完全理解数组,所以可能就是这样,但我在搜索我认为可能是问题的地方时遇到了麻烦。如果您能提供任何帮助,那就太好了!

编辑: 抱歉忘记了,该数组的大小适合保存数据(9 行 x 28 列),并且它没有抛出任何错误或异常......在我发布之前应该已经存在了。

很难确切地说出你在问什么,但我认为你只是想随机分配矩阵中某处的数字 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

所以我刚才展示的这个构造是一个构建块,您可以使用它来创建一个循环 运行s 直到满足条件。

让我们看看它如何适合您的原始示例。我们想要一个 [1, 9) 范围内的随机数,它 not 可以被 3 整除。 random.randint 函数将提供该范围内的随机数,但它 保证它不能被3整除。所以我们需要自己强制执行这个约束。实现这一目标的一种方法是,如果不满足约束条件,则简单地生成一个新数字。

所以现在我们可以使用之前讨论的循环构造来构建一个循环 运行s 直到我们得到一个不能被 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 运算符的 "recursive" 版本。或者,我们可以将其硬编码为期望包含列表的列表。我将采用第二种方法,因为它更简单,我不知道你是否已经熟悉递归,这已经是一个很长的解释了。

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。同样,让我们​​从小处着手,看看条件在所需情况下是否为 True。我们从前面知道 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]

实际上我花了一分钟才弄清楚出了什么问题,但关键的观察是第一次通过循环,你得到了一对成功的 ij ,您将使用 else 分支在嵌套列表中随机放置一个 5。下次成功进入 if 时,您将选择第一个分支,该分支随机将 3 放入嵌套列表中。然而,这里是事情发展的地方。在下一次成功的迭代中,您仍将采用第一个分支,因此您将随机放置 another 3 到嵌套列表中。您将继续这样做多次迭代。最终,您将覆盖之前写入的 5。此时,第一个条件将不再成立,取而代之的是您将开始采用第二个分支,该分支随机将 4 放入嵌套列表中。由于 5 不再出现在嵌套列表中,因此将继续执行此分支。最终,您将在嵌套列表中用 4 覆盖所有先前写入的 3。此时,第二个分支条件将不再成立,您将再次到达 else 分支,将 5 写入嵌套列表。最后,第一个条件将再次成立,因此在下一次成功的迭代中,您将再次写入 3,只要您不走运,就会错过 5,您现在已经满足终止条件对于外部 while 循环,留下一堆 4,只有一个 3 和一个 5。

也许这就是您想要做的,但从您的问题来看似乎并非如此,因此假设您真的只是想要嵌套列表中存在的每个数字之一。如果是这样的话,我们可以很容易地修正之前的程序。我们只需要修复每个 if 案例的条件。我们只想在出现 5 而 出现 3 时采用第一个分支。因此,这给了我们 if contains2d(array, 5) and not contains2d(array, 3):。此外,如果 5 和 3 both 已经存在,我们只想采用第二个分支。因此,这给了我们 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))

这个特定的程序确实有一个缺陷,即后面的值之一可能会覆盖前面的值之一,因此如果您的随机数恰好出现,则所有存在的数字的后置条件可能不成立碰撞。事实上,我最初的回答也有同样的问题。我将把它作为练习留给你解决。