1

I'm attempting to perform a 90 degree clockwise rotation of a 2D matrix like so:

Goal: Rotate CW
[1,2,3]
[4,5,6]
[7,8,9]
----Reverse order of rows in matrix.
    [7,8,9]
    [4,5,6]
    [1,2,3]
----Zip together first elements in each row.
    [7,4,1]
    [8,5,2]
    [9,6,3]

I wrote the following function to do this, which works fine when I run it on my own test data:

# Works on my own test cases
def rotate_matrix(square_matrix):
    square_matrix = zip(*square_matrix[::-1])
    square_matrix = list(map(list, square_matrix))
    return square_matrix

For example:

m1 = [[1, 2],
      [3, 4]]
r1 = rotate_matrix(m1)
# r1:
# [[3, 1]
#  [4, 2]]


m2 = [[1,2,3,4],
      [5,6,7,8],
      [9,10,11,12],
      [13,14,15,16]]
r2 = rotate_matrix(m2)
# r2:
# [[13, 9, 5, 1]
#  [14, 10, 6, 2]
#  [15, 11, 7, 3]
#  [16, 12, 8, 4]]

Now, I'm following the book Elements of Programming Interviews in Python which has a test framework for these questions (located here https://github.com/adnanaziz/EPIJudge) , and I am unable to pass any test cases unless I modify my code to the following:

# Works on EPI test cases
def rotate_matrix(square_matrix):
    square_matrix[:] = zip(*square_matrix[::-1])
    square_matrix[:] = map(list, square_matrix)
    return square_matrix

I thought I understood the idiosyncrasies of python lists, but I am at a loss here. I am trying to modify the list in place, so why is it necessary to use square_matrix[:] rather than square_matrix in the assignment statement?

Again its only when I run this within the EPI test framework that this is a problem. In fact, as soon as I delete the [:] PyCharm tells me "Local variable 'square_matrix' value is not used".

  • If you're trying to modify the list in place, why does your function return anything? – melpomene Feb 16 '19 at 22:50
  • (Without reading through all the tests) Is the function supposed to rotate the matrix in-place or return a rotated version of it? i.e. is there still a `return square_matrix` in the modified version that passes? – martineau Feb 16 '19 at 22:52
  • @melpomene I am returning it in my example to make sure that the list is in fact being modified outside of the function. It isn't necessary. – marcus_afailius Feb 16 '19 at 22:59
  • If you're trying to verify that the list has been modified outside of the function, you need to look at `m2` after the call. What is `r2` good for? – melpomene Feb 16 '19 at 23:04
  • @martineau The matrix should be modified in-place and there is no need for 'return square_matrix` in the code other than for me to print the result to make sure. Deleting that return statement doesn't cause the code above to pass the EPI test cases though. – marcus_afailius Feb 16 '19 at 23:04
  • @melpomene Wow, you're totally right! Printing `square_matrix` within the function or `r2` after the return shows the correct solution. However, if I look at `m2` after the function call, it is unmodified. So my code above does **NOT** modify the list in place. However, that still leaves my original question unanswered: why is the `[:]` necessary in the assignment statement for this to work? I am aware that `[:]` creates a copy of a list, but (1) I don't want a copy and (2) I have never seen `[:]` used on the left-hand side of the assignment statement. – marcus_afailius Feb 16 '19 at 23:09
  • Well—in that case—the failure may be due to the fact that your version returns a new matrix instead of modifying the one it was passed in-place (and the modification make it do that). – martineau Feb 16 '19 at 23:10
  • @martineau Correct - the code above fails because it does not actually modify the list in place (oops). That said, I'm still trying to figure out how using `[:]` in the assignment statement solves this problem. I do not understand the syntax. – marcus_afailius Feb 16 '19 at 23:16
  • It means assign the value on the right side to be the entire contents of the existing `list` instead of creating a completely new (local) one with the name given. `list`s are effectively passed as references in Python. The `[:]` is a "slice" representing all the elements currently in the `list`. See [Slicings](https://docs.python.org/3/reference/expressions.html#slicings) in the documentation. – martineau Feb 16 '19 at 23:25
  • @martineau Thank you. I found [this](https://stackoverflow.com/questions/10623302/how-assignment-works-with-python-list-slice) which I found very helpful. Python treats the slice syntax differently when on the left-hand side of the assignment operator. I am still a bit confused though - if `list`s are effectively passed as references in Python, then why do I need to specify "all elements" using `my_list[:]`? Shouldn't `my_list` point to the same object? – marcus_afailius Feb 17 '19 at 00:21
  • marcus: It has to do with how Python names work. Explaining it well is difficult to do here in a comment, but in a nutshell: Function parameters are essentially local variable names that are initially associated with values when it's called. In your version of the function, the value associated with the variable _name_ `square_matrix` is changed, but that's not the same as changing the value of what it already refers to. That's done by referring to individual elements in it (i.e. `my_list[index] = value`) or by assigning several values at once to a slice of it, i.e. `my_list[:] = values`. – martineau Feb 17 '19 at 01:05

0 Answers0