3

Function to produce powerset gives the correct input. However upon replacing append(curr[:]) with append(curr) gives a list of empty lists. What is the reason?

def subsets(nums):
    def backtrack(first = 0, curr = []):
        # if the combination is done
        if len(curr) == k:  
            output.append(curr)
            return
        for i in range(first, n):
            # add nums[i] into the current combination
            curr.append(nums[i])
            # use next integers to complete the combination
            backtrack(i + 1, curr)
            # backtrack
            curr.pop()
    
    output = []
    n = len(nums)
    for k in range(n + 1):
        backtrack()
    return output
arshovon
  • 13,270
  • 9
  • 51
  • 69
mparsh
  • 41
  • 5
  • In addition to the specific question you've asked here, you might find it useful to look at https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument - there's a chance that the `cur = []` that you're doing there is going to bite you. – Nolen Royalty Jul 09 '20 at 03:41
  • While it is true that `curr[:]` will copy the list and that this is a recognized old habit, since 3.3 there is an explicit `.copy()` method that is preferred instead. – Karl Knechtel Jul 09 '20 at 03:50

3 Answers3

4

As other answers have noted, cur[:] makes a copy of cur. Without making a copy output ends up containing many copies of the same list - each of those lists are modified every time you call cur.append or cur.pop.

The lists end up empty because there is one call to cur.pop for every call of cur.append, which ensures that the "final" iteration of cur is going to be empty.

Nolen Royalty
  • 18,415
  • 4
  • 40
  • 50
2

The difference between arr.append(curr) and arr.append(curr[:]) is that the first one copy the curr list address to arr and the second one copy the values of curr to arr.

Let's see the difference using examples.

First scenario:

curr = [5, 10, 15, 20]
arr = []
arr.append(curr)
print("Before update")
print("Curr: {}\nArr: {}".format(curr, arr))
curr[1] = 7
print("After update")
print("Curr: {}\nArr: {}".format(curr, arr))

Output:

Before update
Curr: [5, 10, 15, 20]
Arr: [[5, 10, 15, 20]]
After update
Curr: [5, 7, 15, 20]
Arr: [[5, 7, 15, 20]]

Second scenario:

curr = [5, 10, 15, 20]
arr = []
print("Before update")
arr.append(curr[:])
print("Curr: {}\nArr: {}".format(curr, arr))
curr[1] = 7
print("After update")
print("Curr: {}\nArr: {}".format(curr, arr))

Output:

Before update
Curr: [5, 10, 15, 20]
Arr: [[5, 10, 15, 20]]
After update
Curr: [5, 7, 15, 20]
Arr: [[5, 10, 15, 20]]

As you can see in first scenario both curr and arr is updated upon changing one index value of the curr list. Because appending the curr to arr holds the same memory address.

In second scenario only curr is updated upon changing one index value of the curr list. Because appending [:] to arr copies the contents to curr list to arr list.

According to said above(theory), the reason why you get an [[], [], [], [], [], [], [], []] when you run the code changing curr for curr[:] is because in the last line curr.pop() in backtrack function you're removing the last elements one by one so you're taking off the elements from the list which is located at same space memory all the time and not from a copy list values of it.

Brad Figueroa
  • 869
  • 6
  • 15
arshovon
  • 13,270
  • 9
  • 51
  • 69
1

Using arr.append(curr[:]) means append a COPY of curr while arr.append(curr) append curr itself to the array arr, meaning when you make any change to curr, it's value in arr will also change correspondingly

You can read more about array copy here: Link

ExplodingGayFish
  • 2,807
  • 1
  • 5
  • 14