0

I have:

def reverseString(self, s: List[str]) -> None:
    s[:] = s[::-1] # Works

... and

def reverseString(self, s: List[str]) -> None:
    s = s[::-1]    # Doesn't work

Where s is a list of characters lets say s = ["k","a","k","a","s","h","i"]

While doing a question on leetcode it rejected when I used s = ... but accepted when I used s[:] = ... and also it was written that DO NOT RETURN ANYTHING but return s.reverse also worked.

cglacet
  • 8,873
  • 4
  • 45
  • 60
  • 1
    Sorry, what is your question? it little bit unclear. – George Imerlishvili Jan 29 '22 at 15:42
  • 1
    yeah sure, [link](https://leetcode.com/problems/reverse-string/) try this question first use s = s[::-1] then use s[:] = s[::-1] they both work the same on my local machine but on the leetcode editor on gets accepted and the other is rejected. So, I want to know the difference between these two. @GiorgiImerlishvili – Vaibhav Mishra Jan 29 '22 at 17:45

1 Answers1

0

This is actually a bit complex and requires two explanations.

First, a python function argument act as label on the passed object. For example, in the following code, arg is the local label (/name) attached to the initial list. When the label arg is re-used, for example by attaching it to a new object (17), the original list is not reachable anymore within function f.

On the other hand, outside of f, the list labeled L is still here, untouched:

def f(arg):
    arg = 17
    print(arg) # 17

L = ['a', 'list']
f(L)
print(L) # ['a', 'list']

That explains why the following function doesn't reverse your list in place:

def reverse_list(arg):
    arg = arg[::-1]
    print(arg) # ['list', 'a']

L = ['a', 'list']
reverse_list(L)
print(L) # ['a', 'list']

This function simply attach the label arg to a new list (that is indeed equal to the reversed list).

Secondly, the difference between arg[:] = ... and arg = ... is that the first will modify the content of the list (instead of attaching the label arg to a new object). This is the reason why the following works as expected:

def alt_reverse_list(arg):
    arg[:] = arg[::-1]

L = ['a', 'list']
alt_reverse_list(L)
print(L) # ['list', 'a']

In this second example we say that the list has been mutated (modified in place). Here is a detailed explanation on slice assignments

For the same reason, calling arg.reverse() would have worked.

Identifying objects

Using the id() function can help figure out what is going on with the argument in the first example (where we don't mutate the list but affect a new value):

def reverse_list(arg):
    print("List ID before: ", id(arg)) 
    arg = arg[::-1]
    print("List ID after: ", id(arg)) 

L = ['a', 'list']
print("Original list ID: ", id(L)) 
reverse_list(L)
print("Final list ID: ", id(L)) 

Which will print something like:

Original list ID:  140395368281088
List ID before:    140395368281088
List ID after:     140395368280447    <--- intruder spotted
Final list ID:     140395368281088

Here we can clearly see that after calling arg = arg[::-1] the object we are manipulating under the name arg is not the same. This shows why the function doesn't have any (side) effect.

cglacet
  • 8,873
  • 4
  • 45
  • 60