在嵌套循环中交换列表变量会导致意外结果(单一数组?)

huangapple go评论73阅读模式
英文:

swapping list variables in nested loop unexpected result (single array?)

问题

如果我有一个类似这样的多维数组它是一个正方形的

[[1, 2, 3],
 [4, 5, 6],
 [7, 8, 9]]

而我想要将它顺时针旋转90度如下所示

[[7, 4, 1],
 [8, 5, 2],
 [9, 6, 3]]

我可以像这样打印出改变后的正方形

for i in range(0,len(a)):
    for j in range(len(a),0,-1):
        print(f"{a[j-1][i]}",end=",")
    print()

输出是

7,4,1,
8,5,2,
9,6,3,

但是如果我尝试在原地重新排列Python列表顺序就会错了

for i in range(0,len(a)):
    x = 0
    for j in range(len(a),0,-1):
        # 不使用临时变量交换的Python技巧
        a[i][x],a[j-1][i] = a[j-1][i],a[i][x]
        x += 1
        
print(a)

输出是

[[3, 6, 1], [8, 5, 2], [9, 4, 7]]
   
为了澄清到目前为止我的想法是为了做到这一点我们进行以下转换

a0 a1 a2 => c0 b0 a0
b0 b1 b2 => c1 b1 a1
c0 c1 c2 => c2 b2 a2

由于评论中提到的问题是因为在移动时我改变了数组单元格多次

我想做的是最多使用O(1)额外内存来改变这个正方形有没有一种方法可以在不使用新数组的情况下实现这个
英文:

If i have a multidimensional array that is a square like this:

[[1, 2, 3],
 [4, 5, 6],
 [7, 8, 9]]

And I want to turn it 90 degrees clockwise like this:

[[7, 4, 1],
 [8, 5, 2],
 [9, 6, 3]]

I can print out the changed square like this:

for i in range(0,len(a)):
    for j in range(len(a),0,-1):
        print(f"{a[j-1][i]}",end=",")
    print()

The output is:

7,4,1,
8,5,2,
9,6,3,

But if I tried to re-arrange the python list in place, I get the wrong order.

for i in range(0,len(a)):
    x = 0
    for j in range(len(a),0,-1):
        # pythonism to swap w/o temp variable         
        a[i][x],a[j-1][i] = a[j-1][i],a[i][x]
        x += 1
        
print(a)

The output is:


[[3, 6, 1], [8, 5, 2], [9, 4, 7]]
   

To clarify, so far my thinking was that in order to do this we make the following transformation:

a0 a1 a2 => c0 b0 a0
b0 b1 b2 => c1 b1 a1
c0 c1 c2 => c2 b2 a2

Because of the comments I see that the issue was because I am changing the array cells more than once as I move it.

What I am trying to do is change the square not more than using O(1) additional memory. Is there a way to do this without using a new array?

答案1

得分: 1

考虑例如四个角值 1, 3, 7, 9
要完成旋转,1 必须移到 3 所在的位置,3 移到 99 移到 7,而 7 移到 1 - 同时进行。

就像通过逐个赋值交换两个元素不起作用一样:

a = 1
b = 2
a = b # `a` 变成了 2
b = a # 哎呀,`b` 没有变成 1,因为这使用了新的 `a`

类似地,无论尝试多少次重复的天真交换元素对,都无法完成四方向的交换:

a, b, c, d = 1, 3, 9, 7 # 要交换的值,顺时针围绕原始值
a, b = b, a # 将 1 放在正确的位置
b, c = c, b # 哎呀,又移动了 1

无论以哪种顺序尝试交换,如果不考虑先前交换的影响,都会出现问题。

实现交换的最简单和最容易理解的方法就是使用 Python 的技巧同时对所有四个元素进行交换:

b, c, d, a = a, b, c, d

只要理解原始的交换技巧是如何工作的,这个方法就显而易见,也很明显它是如何工作的。

通常,奇数大小的正方形可以分解成如下的三角形:

aaaaaaaab
caaaaaabb
ccaaaabbb
cccaabbbb
cccc*bbbb
ccccddbbb
cccddddbb
ccddddddb
cdddddddd

偶数大小的正方形可以分解成如下的形状:

aaaaaaab
caaaaabb
ccaaabbb
cccabbbb
ccccdbbb
cccdddbb
ccdddddb
cddddddd

因此,旋转只是设置循环以遍历 a 区域,找到相应 a 对应的 b/c/d 位置,然后进行交换。这留作练习。

英文:

Consider for example the four corner values 1, 3, 7, 9.
To accomplish the rotation, 1 must move to where 3 is, 3 to 9, 9 to 7 and 7 to 1 - simultaneously.

Just like how swapping two elements by serial assignment does not work:

a = 1
b = 2
a = b # `a` becomes 2
b = a # oops, `b` doesn't become 1, because this uses the new `a`

similarly the 4-way swap cannot be accomplished by repeated, naive swapping of element pairs:

a, b, c, d = 1, 3, 9, 7 # the values to swap, clockwise around the original
a, b = b, a # put the 1 in the right place
b, c = c, b # oops, moved the 1 again

Regardless of the order in which the swaps are attempted, problems will occur if we do not account for the effect of previous swaps.

The simplest and most understandable way to effect the swap is to just use Python's trick for all four elements at once:

b, c, d, a = a, b, c, d

Given a proper understanding of how the original swap trick works, it is both obvious to try this, and obvious how it works.

In general, an odd-sized square can be decomposed into triangles like so:

aaaaaaaab
caaaaaabb
ccaaaabbb
cccaabbbb
cccc*bbbb
ccccddbbb
cccddddbb
ccddddddb
cdddddddd

and an even-sized square like so:

aaaaaaab
caaaaabb
ccaaabbb
cccabbbb
ccccdbbb
cccdddbb
ccdddddb
cddddddd

So the rotation is simply a matter of setting up loops to iterate over the a region, find the corresponding b/c/d positions for a corresponding a, and doing the swap. This is left as an exercise.

答案2

得分: 1

第一条评论:在你的循环中,你总是循环遍历range(0, len(a))。这假设高度和宽度都等于len(a),换句话说,这假定a是一个正方形。然而,你的任务适用于所有矩形,而不仅仅是正方形。因此,最好不要做出这种假设。a的垂直索引应该循环遍历range(0, len(a)),但a的水平索引应该循环遍历range(0, len(a[0]))

第二条评论:与其使用for j in range(len(a),0,-1): print(a[j-1][i],end=","),我建议使用for j in range(len(a)-1,-1,-1): print(a[j][i],end=",")

现在让我们来实际执行任务。

由于你的打印循环有效,你可以将其转化为一个函数,通过“打印到列表”来返回一个新的列表,使用list.append而不是print。只需将以下内容替换为:

  • print(x, end='') 替换为 row.append(x)
  • print() 替换为 result.append(row); row=[]
a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] # 正方形
b = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]] # 非正方形矩形

def rotate_clockwise_append(a):
    height, width = len(a), len(a[0])
    result = []
    row = []
    for i in range(0,width):
        for j in range(height-1,-1,-1):
            row.append(a[j][i])
        result.append(row)
        row = []
    return result

print(rotate_clockwise_append(a)) # [[7, 4, 1], [8, 5, 2], [9, 6, 3]]
print(rotate_clockwise_append(b)) # [[9, 5, 1], [10, 6, 2], [11, 7, 3], [12, 8, 4]]

在Python中,通常可以使用列表推导而不是在循环中使用.append来构建列表:

def rotate_clockwise_listcomp(a):
    height, width = len(a), len(a[0])
    return [
        [a[j][i] for j in range(height-1,-1,-1)]
        for i in range(0,width)
    ]

print(rotate_clockwise_listcomp(a)) # [[7, 4, 1], [8, 5, 2], [9, 6, 3]]
print(rotate_clockwise_listcomp(b)) # [[9, 5, 1], [10, 6, 2], [11, 7, 3], [12, 8, 4]]

你还可以使用内置函数来执行反转操作:

def rotate_clockwise_builtin(a):
    return list(zip(*reversed(a)))  # 或者如果担心得到元组的列表,可以使用 list(map(list,zip(*reversed(a)))

print(rotate_clockwise_builtin(a)) # [(7, 4, 1), (8, 5, 2), (9, 6, 3)]
print(rotate_clockwise_builtin(b)) # [(9, 5, 1), (10, 6, 2), (11, 7, 3), (12, 8, 4)]

如果你经常处理矩形数组,通常建议使用NumPy而不是内置Python列表:

import numpy as np

def rotate_clockwise_numpy1(a):
    return np.flip(a, axis=0).T  # 垂直翻转,然后转置

def rotate_clockwise_numpy2(a):
    return np.flip(np.transpose(a), axis=1)  # 转置,然后水平翻转

print(rotate_clockwise_numpy1(a))
# [[7 4 1]
#  [8 5 2]
#  [9 6 3]]
print(rotate_clockwise_numpy1(b))
# [[ 9  5  1]
#  [10  6  2]
#  [11  7  3]
#  [12  8  4]]

print(rotate_clockwise_numpy2(a))
# [[7 4 1]
#  [8 5 2]
#  [9 6 3]]
print(rotate_clockwise_numpy2(b))
# [[ 9  5  1]
#  [10  6  2]
#  [11  7  3]
#  [12  8  4]]
英文:

First comment: in your loops, you always loop over range(0, len(a)). This assumes that the height and width are both equal to len(a), in other words this assume that a is a square. However, your task makes sense for all rectangles, not just for squares. So, it is better not to make this assumption. Vertical indices for a should loop over range(0,len(a)), but horizontal indices for a should loop over range(0, len(a[0])).

Second comment: rather than for j in range(len(a),0,-1): print(a[j-1][i],end=","), I recommend for j in range(len(a)-1,-1,-1): print(a[j][i],end=",")

Now on to actually performing the task.

Since your printing-loop works, you can turn it into a function that returns a new list of lists, by "printing into a list", using list.append instead of print. Just replace:

  • print(x, end='') with row.append(x);
  • print() with result.append(row); row=[]:
a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] # square
b = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]] # non-square rectangle

def rotate_clockwise_append(a):
    height, width = len(a), len(a[0])
    result = []
    row = []
    for i in range(0,width):
        for j in range(height-1,-1,-1):
            row.append(a[j][i])
        result.append(row)
        row = []
    return result

print(rotate_clockwise_append(a)) # [[7, 4, 1], [8, 5, 2], [9, 6, 3]]
print(rotate_clockwise_append(b)) # [[9, 5, 1], [10, 6, 2], [11, 7, 3], [12, 8, 4]]

In python, it's always possible to build a list using a list comprehension, rather than using .append in a loop:

def rotate_clockwise_listcomp(a):
    height, width = len(a), len(a[0])
    return [
        [a[j][i] for j in range(height-1,-1,-1)]
        for i in range(0,width)
    ]

print(rotate_clockwise_listcomp(a)) # [[7, 4, 1], [8, 5, 2], [9, 6, 3]]
print(rotate_clockwise_listcomp(b)) # [[9, 5, 1], [10, 6, 2], [11, 7, 3], [12, 8, 4]]

You can also use builtin functions to do the reversal operations for you:

def rotate_clockwise_builtin(a):
    return list(zip(*reversed(a)))  # or list(map(list,zip(*reversed(a)))) if you're afraid of a list of tuples

print(rotate_clockwise_builtin(a)) # [(7, 4, 1), (8, 5, 2), (9, 6, 3)]
print(rotate_clockwise_builtin(b)) # [(9, 5, 1), (10, 6, 2), (11, 7, 3), (12, 8, 4)]

If you deal with rectangle arrays a lot, it is often recommended to use numpy rather than builtin python lists:

import numpy as np

def rotate_clockwise_numpy1(a):
    return np.flip(a, axis = 0).T  # flip vertically, then transpose

def rotate_clockwise_numpy2(a):
    return np.flip(np.transpose(a), axis=1)  # transpose, then flip horizontally

print(rotate_clockwise_numpy1(a))
# [[7 4 1]
#  [8 5 2]
#  [9 6 3]]
print(rotate_clockwise_numpy1(b))
# [[ 9  5  1]
#  [10  6  2]
#  [11  7  3]
#  [12  8  4]]

print(rotate_clockwise_numpy2(a))
# [[7 4 1]
#  [8 5 2]
#  [9 6 3]]
print(rotate_clockwise_numpy2(b))
# [[ 9  5  1]
#  [10  6  2]
#  [11  7  3]
#  [12  8  4]]

huangapple
  • 本文由 发表于 2023年2月27日 16:10:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/75578069.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定