1

can someone plz tell me why this code generate empty "res" variable? If uncomment the commented line and remove below will be working fine.

Codes not work:

class Solution(object):
    def dfs(self, nums, res, line):
        if not nums:
            print(line)
            res.append(line)
            return

        for i, num in enumerate(nums):
            line.append(num)
            # self.dfs(nums[:i]+nums[i+1:], res, line+[num])  
            self.dfs(nums[:i]+nums[i+1:], res, line)
            line.pop()

    def permute(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        res = []
        self.dfs(nums, res, [])
        print(res)
        return res

if __name__ == "__main__":
    Solution().permute([1,2])

Works fine if changing to:

 for i, num in enumerate(nums):
     self.dfs(nums[:i]+nums[i+1:], res, line+[num])  

except using append/pop for passing the DFS. Even the "line" variable before appending to "res" is correct.

Does it have something to do with referencing? The only thing I could think of is whatever passed to res got cleaned up. I would really appreciate if someone could show me the link to reference.

Dominique Fortin
  • 2,212
  • 15
  • 20
CSY
  • 543
  • 1
  • 6
  • 11

2 Answers2

0

Your guessing is correct. Just like Java, list in python is copy-by-reference. Consider the following example:

a = [1, 2, 3]
b = a
a.append(4)
print(b)

You will get

[1, 2, 3, 4]

Thus, one easy way to fix the code is that replacing the res.append(line) in dfs() right before return into:

res.append(line[:])

Basically, line[:] will create a separate list object. You can see more information in this thread.

Community
  • 1
  • 1
TimeString
  • 1,778
  • 14
  • 25
  • thanks for your answer. so in this example, after DFS call finished, line variable got cleaned up so does the one in the res[]. but why the new list object "list+[num]" doesn't get cleaned up? are they the same type of local variables that only exist in the DFS call? – CSY May 31 '16 at 22:52
  • No, like what I said, if the passing argument is a list (or an object in general), it is pass-by-reference, that means the physical object is still there after the end of the function call, i.e., dfs(). I'm not too sure about this part, but I believe python implements some garbage collection mechanisms. Unless the list object is not referenced by any variable, the object will keep alive. – TimeString May 31 '16 at 22:59
  • Python call semantics are [not call by reference](https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_reference) because the receiving end gets the object, not a reference to it. – bignose Jun 01 '16 at 00:43
0

Python function call semantics:

  • Are not pass-by-reference. The receiving end does not need to de-reference it.

  • Are not pass-by-value. There is no copy made of the value.

  • May best be described as pass by object. The object itself is passed to the function; not a reference, not a copy, the very object itself.

See Frederik Lundh's article about call by object semantics. The Wikipedia article for this names it “call by sharing” and attributes Barbara Liskov with coining that term.

This means that any container object you pass as a parameter will be present in the function as the same object, and modifications there will persist in the container object. The same is true of any mutable object.

So, if you want to modify a container passed in, don't modify it directly. Create a copy (usually by calling the type, e.g. list, to get a new instance), and modify that.

If the function's purpose is to generate a new container, it should create its own instance and return that copy.

So I think the dfs function is poorly designed. It should instead do something like:

def dfs(nums, line):
    result = []

    for i, num in enumerate(nums):
        line.append(num)
        result.extend(dfs(nums[:i]+nums[i+1:], line))
        line.pop()
    else:
        print(line)
        result.append(line)

    return result
bignose
  • 30,281
  • 14
  • 77
  • 110