2

Consider the following piece of code that generates all subsets of size k of an array [1,2,3,...,n]:

def combinations(n, k):
   result = []
   directed_combinations(n, k, 1, [], result)
   return result

def directed_combinations(n, k, offset, partial_combination, result):
    if len(partial_combination) == k:
        new_partial = [x for x in partial_combination]
        result.append(new_partial)
        return
    num_remaining = k - len(partial_combination)
    i = offset
    #                kind of checks if expected num remaining is no greater than actual num remaining
    while i <= n and num_remaining <= n - i + 1:
        partial_combination.append(i)
        directed_combinations(n, k, i + 1, partial_combination, result)
        del partial_combination[-1]
        # partial_combination = partial_combination[:-1] <-- same funcationality as line above, but produces weird bug.
        i += 1

print(combinations(n=4,k=2))

For example, combinations(n=4,k=2) will generate all subsets of length 2 of [1,2,3,4]. There are two lines in the code that produce a list with the last element removed. I tried accomplishing it with del and creating a brand new list by slicing off the last element (i.e. [-1]). The version with del produces the correct result. But, version with [-1] doesn't. There is no runtime error; just a logical bug (i.e. incorrect result).

I suspect this has something to do with creating a new list when doing slicing vs. keeping the same list with del. I can't seem to understand why this is an issue.

1 Answer 1

4

I didn't notice at first that your function is recursive (should've read your tags better).

You're right, functionally the two are almost the same. Here is the exact same thing:

# del partial_combination[-1]                     # working (mutate)
# partial_combination = partial_combination[:-1]  # different (rebind)
partial_combination[:] = partial_combination[:-1] # same (mutate)

The result of each of the above will be that you end up with a list containing the same elements. But while del and partial_combination[:] mutate your original list, the middle one rebinds the name to a new list with the same elements. When you pass on this new list to the next recursive step, it will operate on its own copy rather than on the single list the previous recursive levels are working on.

To prove this, you can call print(id(partial_combination)) after each of the above options, and see that the id changes in the rebinding case, while it stays the same throughout the mutating ones.

Sign up to request clarification or add additional context in comments.

2 Comments

Okay. It makes sense. However, at first, I was confused why the last option mutated the list rather than creating a new one. I've never seen assignment to slices before. Checking the docs (3.1.3 section) clarified it for me.
@AzaTulepbergenov here's an additional source for understanding mutating vs rebinding.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.