3

In short. How do I write something else than this: for another in combinationOfK(K-1, L[i+1:]): My function combinationOfK(...) is not iterable.

I am trying to understand the code from here, solution to. Problem 26: Generate the combinations of K distinct objects chosen from the N elements of a list
I know what yield does. But I am trying to write the code without a yield statement. Code with yield statement is this.

def combination(K, L):
    if K<=0:
        yield []
        return
    for i in range(len(L)):
        thisone = L[i:i+1]
        for another in combination(K-1, L[i+1:]):
            yield thisone + another

The question, yield-keyword-explained gave me the idea that I could replace yield. The recepie they give, which is not working for me, is:

When you see a function with yield statements, apply this easy trick to understand what will happen:

  1. Insert a line result = [] at the start of the function.
  2. Replace each yield expr with result.append(expr).
  3. Insert a line return result at the bottom of the function.
  4. Yay - no more yield statements! Read and figure out code.
  5. Revert function to original definition.

Using this to get code without yield give me this. The code is not working (the function is not iterable). What do I have to write to get this code working without yield?

def combinationOfK(K,L):
    result = []
    if K <= 0:
        result.append([])
        return
    for i in range(len(L)):
        thisone = L[i:i+1]
        for another in combinationOfK(K-1, L[i+1:]):  # the error
            result.append(thisone + another)
    return result

I am using this code to test the function,

the_list = ['a','b','c','d','e']
print list(combinationOfK(2, the_list))

raising error TypeError: 'NoneType' object is not iterable.

Community
  • 1
  • 1
r4.
  • 358
  • 1
  • 6
  • 22

2 Answers2

2

As Vincent mentioned, your function is returning None because of the 5th line. Change it to this:

def combinationOfK(K,L):
    result = []
    if K <= 0:
        result.append([])
        return result
    for i in range(len(L)):
        thisone = L[i:i+1]
        for another in combinationOfK(K-1, L[i+1:]):  # the error
            result.append(thisone + another)
    return result

However, why are you against yield? Generators make for readable, efficient code. The point of the Yield Keyword Explained article was not to dispense with it, but rather, to explain it.

In the generator code you posted:

def combination(K, L):
    if K<=0:
        yield []
        return
    for i in range(len(L)):
        thisone = L[i:i+1]
        for another in combination(K-1, L[i+1:]):
            yield thisone + another

The return statement does NOT mean the same thing as return does in a normal function. In a generator, return immediately raises StopIteration, which causes the caller to stop iterating over the generator object.

Colin Dunklau
  • 3,001
  • 1
  • 20
  • 19
2

The problem is that your original code uses return in an unusual way.

def combination(K, L):
    if K<=0:
        yield []
        return    #  <--- hmmm

Most of the time you won't see return in a generator, because you don't often need it. Usually, generators simply "fall off" at the end; the interpreter reaches the end of the generator without encountering a return statement, and then it knows to throw StopIteration.

In this case, the writer of the code has inserted a return statement to "hurry up" the process. When K <= 0, there's no more work to be done, so the generator can throw StopIteration -- but without the return statement, it would go into the for loop, producing incorrect results. In my opinion, a clearer way to do this would have been like so:

def combination(K, L):
    if K<=0:
        yield []
    else:
        for i in range(len(L)):
            thisone = L[i:i+1]
            for another in combination(K-1, L[i+1:]):
                yield thisone + another

Now the conversion works as expected:

def combination2(K, L):
    result = []
    if K <= 0:
        result.append([])
    else:
        for i in range(len(L)):
            thisone = L[i:i + 1]
            for another in combination2(K - 1, L[i + 1:]):
                result.append(thisone + another)
    return result
senderle
  • 145,869
  • 36
  • 209
  • 233