Python: using a recursive algorithm as a generator - python

Recently I wrote a function to generate certain sequences with nontrivial constraints. The problem came with a natural recursive solution. Now it happens that, even for relatively small input, the sequences are several thousands, thus I would prefer to use my algorithm as a generator instead of using it to fill a list with all the sequences.
Here is an example. Suppose we want to compute all the permutations of a string with a recursive function. The following naive algorithm takes an extra argument 'storage' and appends a permutation to it whenever it finds one:
def getPermutations(string, storage, prefix=""):
if len(string) == 1:
storage.append(prefix + string) # <-----
else:
for i in range(len(string)):
getPermutations(string[:i]+string[i+1:], storage, prefix+string[i])
storage = []
getPermutations("abcd", storage)
for permutation in storage: print permutation
(Please don't care about inefficiency, this is only an example.)
Now I want to turn my function into a generator, i.e. to yield a permutation instead of appending it to the storage list:
def getPermutations(string, prefix=""):
if len(string) == 1:
yield prefix + string # <-----
else:
for i in range(len(string)):
getPermutations(string[:i]+string[i+1:], prefix+string[i])
for permutation in getPermutations("abcd"):
print permutation
This code does not work (the function behaves like an empty generator).
Am I missing something?
Is there a way to turn the above recursive algorithm into a generator without replacing it with an iterative one?

def getPermutations(string, prefix=""):
if len(string) == 1:
yield prefix + string
else:
for i in xrange(len(string)):
for perm in getPermutations(string[:i] + string[i+1:], prefix+string[i]):
yield perm
Or without an accumulator:
def getPermutations(string):
if len(string) == 1:
yield string
else:
for i in xrange(len(string)):
for perm in getPermutations(string[:i] + string[i+1:]):
yield string[i] + perm

This avoids the len(string)-deep recursion, and is in general a nice way to handle generators-inside-generators:
from types import GeneratorType
def flatten(*stack):
stack = list(stack)
while stack:
try: x = stack[0].next()
except StopIteration:
stack.pop(0)
continue
if isinstance(x, GeneratorType): stack.insert(0, x)
else: yield x
def _getPermutations(string, prefix=""):
if len(string) == 1: yield prefix + string
else: yield (_getPermutations(string[:i]+string[i+1:], prefix+string[i])
for i in range(len(string)))
def getPermutations(string): return flatten(_getPermutations(string))
for permutation in getPermutations("abcd"): print permutation
flatten allows us to continue progress in another generator by simply yielding it, instead of iterating through it and yielding each item manually.
Python 3.3 will add yield from to the syntax, which allows for natural delegation to a sub-generator:
def getPermutations(string, prefix=""):
if len(string) == 1:
yield prefix + string
else:
for i in range(len(string)):
yield from getPermutations(string[:i]+string[i+1:], prefix+string[i])

The interior call to getPermutations -- it's a generator, too.
def getPermutations(string, prefix=""):
if len(string) == 1:
yield prefix + string
else:
for i in range(len(string)):
getPermutations(string[:i]+string[i+1:], prefix+string[i]) # <-----
You need to iterate through that with a for-loop (see #MizardX posting, which edged me out by seconds!)

Related

Recursive function to obtain non-repeated characters from string

I have this exercise:
Write a recursive function that takes a string and returns all the characters that are not repeated in said string.
The characters in the output don't need to have the same order as in the input string.
First I tried this, but given the condition for the function to stop, it never evaluates the last character:
i=0
lst = []
def list_of_letters_rec(str=""):
if str[i] not in lst and i < len(str) - 1:
lst.append(str[i])
list_of_letters_rec(str[i+1:])
elif str[i] in lst and i < len(str) - 1:
list_of_letters_rec(str[i+1:])
elif i > len(str) - 1:
return lst
return lst
word = input(str("Word?"))
print(list_of_letters_rec(word))
The main issue with this function is that it never evaluates the last character.
An example of an output:
['a', 'r', 'd', 'v'] for input 'aardvark'.
Since the characters don't need to be ordered, I suppose a better approach would be to do the recursion backwards, and I also tried another approach (below), but no luck:
lst = []
def list_of_letters_rec(str=""):
n = len(str) - 1
if str[n] not in lst and n >= 0:
lst.append(str[n])
list_of_letters_rec(str[:n-1])
elif str[n] in lst and n >= 0:
list_of_letters_rec(str[:n-1])
return lst
word = input(str("Word?"))
print(list_of_letters_rec(word))
Apparently, the stop conditions are not well defined, especially in the last one, as the output I get is
IndexError: string index out of range
Could you give me any hints to help me correct the stop condition, either in the 1st or 2nd try?
You can try:
word = input("> ")
result = [l for l in word if word.count(l) < 2]
> aabc
['b', 'c']
Demo
One improvement I would offer on #trincot's answer is the use of a set, which has better look-up time, O(1), compared to lists, O(n).
if the input string, s, is empty, return the empty result
(inductive) s has at least one character. if the first character, s[0] is in the memo, mem, the character has already been seen. Return the result of the sub-problem, s[1:]
(inductive) The first character is not in the memo. Add the first character to the memo and prepend the first character to the result of the sub-problem, s[1:]
def list_of_letters(s, mem = set()):
if not s:
return "" #1
elif s[0] in mem:
return list_of_letters(s[1:], mem) #2
else:
return s[0] + list_of_letters(s[1:], {*mem, s[0]}) #3
print(list_of_letters("aardvark"))
ardvk
Per your comment, the exercise asks only for a string as input. We can easily modify our program to privatize mem -
def list_of_letters(s): # public api
def loop(s, mem): # private api
if not s:
return ""
elif s[0] in mem:
return loop(s[1:], mem)
else:
return s[0] + loop(s[1:], {*mem, s[0]})
return loop(s, set()) # run private func
print(list_of_letters("aardvark")) # mem is invisible to caller
ardvk
Python's native set data type accepts an iterable which solves this problem instantly. However this doesn't teach you anything about recursion :D
print("".join(set("aardvark")))
akdrv
Some issues:
You miss the last character because of i < len(str) - 1 in the conditionals. That should be i < len(str) (but read the next points, as this still needs change)
The test for if i > len(str) - 1 should come first, before doing anything else, otherwise you'll get an invalid index reference. This also makes the other conditions on the length unnecessary.
Don't name your variable str, as that is already a used name for the string type.
Don't populate a list that is global. By doing this, you can only call the function once reliably. Any next time the list will still have the result of the previous call, and you'll be adding to that. Instead use the list that you get from the recursive call. In the base case, return an empty list.
The global i has no use, since you never change its value; it is always 0. So you should just reference index [0] and check that the string is not empty.
Here is your code with those corrections:
def list_of_letters_rec(s=""):
if not s:
return []
result = list_of_letters_rec(s[1:])
if s[0] not in result:
result.append(s[0])
return result
print(list_of_letters_rec("aardvark"))
NB: This is not the most optimal way to do it. But I guess this is what you are asked to do.
A possible solution would be to just use an index instead of splicing the string:
def list_of_letters_rec(string="", index = 0, lst = []):
if(len(string) == index):
return lst
char = string[index]
if string.count(char) == 1:
lst.append(char)
return list_of_letters_rec(string, index+1, lst)
word = input(str("Word?"))
print(list_of_letters_rec(word))

How to write a function that recursively removes instances of a character from a list?

I have to write a function that takes a list and char gets a list with all instances of the char removed.
I'm just not quite getting anywhere with the sublists. I've been programming in Java for some time but I'm new to python.
My code:
def my_remove(the_char, the_list):
if the_list == []:#works
return the_list
if isinstance(the_list[0],list): #if the first element in the list is, itself, a list
#remove the character from the first element/list and move on to other list elements in the main list
else:
print 'else'
return the_list
print 'regular return'
return the_list
The key to this recursive problem (and most) is understanding mathematical induction -
def my_remove(the_char, the_list):
if the_list == []: #1
return the_list
elif isinstance(the_list[0],list): #2
return my_remove(the_char, the_list[0]) \
+ my_remove(the_char, the_list[1:])
elif the_list[0] == the_char: #3
return my_remove(the_char, the_list[1:])
else: #4
return [ the_list[0] ] + my_remove(the_char, the_list[1:])
input = ['a','z',['z','b',['c','z','z']],[['d']],'z']
print(my_remove('z', input))
# ['a','b','c','d']
In the numbered comments -
terminating condition and base case: when the input is empty, return an empty output
otherwise, by induction, the list is not empty. If the first element of the list is another list, combine the result of calling my_remove on the first element and my_remove on the tail of the list, list[1:]
otherwise, by induction, the list is not empty and the first element of the list is not a list. If the first element matches the char, simply call my_remove on the tail of the list.
otherwise, by induction, the list is not empty and the first element is of the list is not a list, and the first element of the list does not match the char. Include the first element of the list in the output and combine it with my_remove called on the tail of the list
Your question explicitly says that helper functions cannot be used. This is most likely an indication of a bad programming teacher. Helper functions make it possible to remove complexity from your program, thereby gaining a complexity-free mind.
Given some generic functions for working on lists -
def isEmpty(l):
return len(l) == 0
def isList(l):
return isinstance(l, list)
def head(l):
return l[0]
def tail(l):
return l[1:]
We can write my_remove with a richer semantics that immediately communicates its intentions to the reader -
def my_remove(x, lst):
if isEmpty(lst):
return lst
elif isList(head(lst)):
return my_remove(x, head(lst)) \
+ my_remove(x, tail(lst))
elif head(lst) == x:
return my_remove(x, tail(lst))
else:
return [ head(lst) ] + my_remove(x, tail(lst))
The output is the same, of course -
print(my_remove('z', input))
# ['a','b','c','d']
These helpers could be further improved to protect the programmer for their misuse. Ie, raising a RuntimeWarning is a good way to let you know you made a mistake with your inductive reasoning -
def isEmpty(l):
return isList(l) and len(l) == 0
def isList(l):
return isinstance(l, list)
def head(l):
if isEmpty(l):
raise RuntimeWarning('head called on empty list')
else:
return l[0]
def tail(l):
if isEmpty(l):
raise RuntimeWarning('tail called on empty list')
else:
return l[1:]
There are two cases to handle:
when word is list
when word is str
For case 1, when word is an empty list (base case) return an empty list.
Return the concatenated list of a list of the function invoked with the first item in the list and the result of the function invoked with the remaining items in the list.
For case 2, when the word is an empty string (base case) return an empty string,
when the first character of the word matched the character searched for, return the result of calling the recursive function with the rest of the string.
Otherwise, concatenate the first character together with the result of calling the recursive function with the rest of the string.
import doctest
def remove(word, char):
"""
>>> remove([['swan'], ['elephant'], 'snake'], 'e')
[['swan'], ['lphant'], 'snak']
>>> remove(['swan', [['elephant']], ['snake']], 'e')
['swan', [['lphant']], ['snak']]
"""
if isinstance(word, list):
if word == []:
return []
head, tail = word[0], word[1:]
return [remove(head, char)] + remove(tail, char)
if isinstance(word, str):
if word == '':
return ''
head, tail = word[0], word[1:]
if head == char:
return remove(tail, char)
return head + remove(tail, char)
doctest.testmod()

How to test if a sequence starts with values in another sequence?

How to simply test, if a sequence starts with certain values in Python3?
There's of course the solution of writing a custom function, example below. But does Python itself offer some solution? Something more pythonic, an understandable one-liner would be good.
def startswith(sequence, prefix):
"""return true if sequence starts with items in prefix"""
if len(prefix) > len(sequence):
return False
for i in range(len(prefix)):
if sequence[i] != prefix[i]:
return False
return True
len(prefix) <= len(sequence) and all(i==j for i, j in zip(prefix, sequence))

Count the number of times an item occurs in a sequence using recursion Python

I'm trying to count the number of times an item occurs in a sequence whether it's a list of numbers or a string, it works fine for numbers but i get an error when trying to find a letter like "i" in a string:
def Count(f,s):
if s == []:
return 0
while len(s) != 0:
if f == s[0]:
return 1 + Count(f,s[1:])
else:
return 0 + Count(f,s[1:])
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
There's a far more idiomatic way to do it than using recursion: use the built-in count method to count occurrences.
def count(str, item):
return str.count(item)
>>> count("122333444455555", "4")
4
However, if you want to do it with iteration, you can apply a similar principle. Convert it to a list, then iterate over the list.
def count(str, item):
count = 0
for character in list(str):
if character == item:
count += 1
return count
The problem is your first if, which explicitly checks if the input is an empty list:
if s == []:
return 0
If you want it to work with strs and lists you should simply use:
if not s:
return s
In short any empty sequence is considered false according to the truth value testing in Python and any not-empty sequence is considered true. If you want to know more about it I added a link to the relevant documentation.
You can also omit the while loop here because it's unnecessary because it will always return in the first iteration and therefore leave the loop.
So the result would be something along these lines:
def count(f, s):
if not s:
return 0
elif f == s[0]:
return 1 + count(f, s[1:])
else:
return 0 + count(f, s[1:])
Example:
>>> count('i', 'what is it')
2
In case you're not only interested in making it work but also interested in making it better there are several possibilities.
Booleans subclass from integers
In Python booleans are just integers, so they behave like integers when you do arithmetic:
>>> True + 0
1
>>> True + 1
2
>>> False + 0
0
>>> False + 1
1
So you can easily inline the if else:
def count(f, s):
if not s:
return 0
return (f == s[0]) + count(f, s[1:])
Because f == s[0] returns True (which behaves like a 1) if they are equal or False (behaves like a 0) if they aren't. The parenthesis are not necessary but I added them for clarity. And because the base case always returns an integer this function itself will always return an integer.
Avoiding copies in the recursive approach
Your approach will create a lot of copies of the input because of the:
s[1:]
This creates a shallow copy of the whole list (or string, ...) except for the first element. That means you actually have an operation that uses O(n) (where n is the number of elements) time and memory in every function call and because you do this recursively the time and memory complexity will be O(n**2).
You can avoid these copies, for example, by passing the index in:
def _count_internal(needle, haystack, current_index):
length = len(haystack)
if current_index >= length:
return 0
found = haystack[current_index] == needle
return found + _count_internal(needle, haystack, current_index + 1)
def count(needle, haystack):
return _count_internal(needle, haystack, 0)
Because I needed to pass in the current index I added another function that takes the index (I assume you probably don't want the index to be passed in in your public function) but if you wanted you could make it an optional argument:
def count(needle, haystack, current_index=0):
length = len(haystack)
if current_index >= length:
return 0
return (haystack[current_index] == needle) + count(needle, haystack, current_index + 1)
However there is probably an even better way. You could convert the sequence to an iterator and use that internally, at the start of the function you pop the next element from the iterator and if there is no element you end the recursion, otherwise you compare the element and then recurse into the remaining iterator:
def count(needle, haystack):
# Convert it to an iterator, if it already
# is an (well-behaved) iterator this is a no-op.
haystack = iter(haystack)
# Try to get the next item from the iterator
try:
item = next(haystack)
except StopIteration:
# No element remained
return 0
return (item == needle) + count(needle, haystack)
Of course you could also use an internal method if you want to avoid the iter call overhead that is only necessary the first time the function is called. However that's a micro-optimization that may not result in noticeably faster execution:
def _count_internal(needle, haystack):
try:
item = next(haystack)
except StopIteration:
return 0
return (item == needle) + _count_internal(needle, haystack)
def count(needle, haystack):
return _count_internal(needle, iter(haystack))
Both of these approaches have the advantage that they don't use (much) additional memory and can avoid the copies. So it should be faster and take less memory.
However for long sequences you will run into problems because of the recursion. Python has a recursion-limit (which is adjustable but only to some extend):
>>> count('a', 'a'*10000)
---------------------------------------------------------------------------
RecursionError Traceback (most recent call last)
<ipython-input-9-098dac093433> in <module>()
----> 1 count('a', 'a'*10000)
<ipython-input-5-5eb7a3fe48e8> in count(needle, haystack)
11 else:
12 add = 0
---> 13 return add + count(needle, haystack)
... last 1 frames repeated, from the frame below ...
<ipython-input-5-5eb7a3fe48e8> in count(needle, haystack)
11 else:
12 add = 0
---> 13 return add + count(needle, haystack)
RecursionError: maximum recursion depth exceeded in comparison
Recursion using divide-and-conquer
There are ways to mitigate (you cannot solve the recursion depth problem as long as you use recursion) that problem. An approach used regularly is divide-and-conquer. It basically means you divide whatever sequence you have into 2 (sometimes more) parts and do call the function with each of these parts. The recursion sill ends when only one item remained:
def count(needle, haystack):
length = len(haystack)
# No item
if length == 0:
return 0
# Only one item remained
if length == 1:
# I used the long version here to avoid returning True/False for
# length-1 sequences
if needle == haystack[0]:
return 1
else:
return 0
# More than one item, split the sequence in
# two parts and recurse on each of them
mid = length // 2
return count(needle, haystack[:mid]) + count(needle, haystack[mid:])
The recursion depth now changed from n to log(n), which allows to make the call that previously failed:
>>> count('a', 'a'*10000)
10000
However because I used slicing it will again create lots of copies. Using iterators will be complicated (or impossible) because iterators don't have a size (generally) but it's easy to use indices:
def _count_internal(needle, haystack, start_index, end_index):
length = end_index - start_index
if length == 0:
return 0
if length == 1:
if needle == haystack[start_index]:
return 1
else:
return 0
mid = start_index + length // 2
res1 = _count_internal(needle, haystack, start_index, mid)
res2 = _count_internal(needle, haystack, mid, end_index)
return res1 + res2
def count(needle, haystack):
return _count_internal(needle, haystack, 0, len(haystack))
Using built-in methods with recursion
It may seem stupid to use built-in methods (or functions) in this case because there is already a built-in method to solve the problem without recursion but here it is and it uses the index method that both strings and lists have:
def count(needle, haystack):
try:
next_index = haystack.index(needle)
except ValueError: # the needle isn't present
return 0
return 1 + count(needle, haystack[next_index+1:])
Using iteration instead of recursion
Recursion is really powerful but in Python you have to fight against the recursion limit and because there is not tail call optimization in Python it is often rather slow. This can be solved by using iterations instead of recursion:
def count(needle, haystack):
found = 0
for item in haystack:
if needle == item:
found += 1
return found
Iterative approaches using built-ins
If you're more advantageous, one can also use a generator expression together with sum:
def count(needle, haystack):
return sum(needle == item for item in haystack)
Again this relies on the fact that booleans behave like integers and so sum adds all the occurrences (ones) with all non-occurrences (zeros) and thus gives the number of total counts.
But if one is already using built-ins it would be a shame not to mention the built-in method (that both strings and lists have): count:
def count(needle, haystack):
return haystack.count(needle)
At that point you probably don't need to wrap it inside a function anymore and could simply use just the method directly.
In case you even want to go further and count all elements you can use the Counter in the built-in collections module:
>>> from collections import Counter
>>> Counter('abcdab')
Counter({'a': 2, 'b': 2, 'c': 1, 'd': 1})
Performance
I often mentioned copies and their effect on memory and performance and I actually wanted to present some quantitative results to show that it actually makes a difference.
I used a fun-project of mine simple_benchmarks here (it's a third-party package so if you want to run it you have to install it):
def count_original(f, s):
if not s:
return 0
elif f == s[0]:
return 1 + count_original(f, s[1:])
else:
return 0 + count_original(f, s[1:])
def _count_index_internal(needle, haystack, current_index):
length = len(haystack)
if current_index >= length:
return 0
found = haystack[current_index] == needle
return found + _count_index_internal(needle, haystack, current_index + 1)
def count_index(needle, haystack):
return _count_index_internal(needle, haystack, 0)
def _count_iterator_internal(needle, haystack):
try:
item = next(haystack)
except StopIteration:
return 0
return (item == needle) + _count_iterator_internal(needle, haystack)
def count_iterator(needle, haystack):
return _count_iterator_internal(needle, iter(haystack))
def count_divide_conquer(needle, haystack):
length = len(haystack)
if length == 0:
return 0
if length == 1:
if needle == haystack[0]:
return 1
else:
return 0
mid = length // 2
return count_divide_conquer(needle, haystack[:mid]) + count_divide_conquer(needle, haystack[mid:])
def _count_divide_conquer_index_internal(needle, haystack, start_index, end_index):
length = end_index - start_index
if length == 0:
return 0
if length == 1:
if needle == haystack[start_index]:
return 1
else:
return 0
mid = start_index + length // 2
res1 = _count_divide_conquer_index_internal(needle, haystack, start_index, mid)
res2 = _count_divide_conquer_index_internal(needle, haystack, mid, end_index)
return res1 + res2
def count_divide_conquer_index(needle, haystack):
return _count_divide_conquer_index_internal(needle, haystack, 0, len(haystack))
def count_index_method(needle, haystack):
try:
next_index = haystack.index(needle)
except ValueError: # the needle isn't present
return 0
return 1 + count_index_method(needle, haystack[next_index+1:])
def count_loop(needle, haystack):
found = 0
for item in haystack:
if needle == item:
found += 1
return found
def count_sum(needle, haystack):
return sum(needle == item for item in haystack)
def count_method(needle, haystack):
return haystack.count(needle)
import random
import string
from functools import partial
from simple_benchmark import benchmark, MultiArgument
funcs = [count_divide_conquer, count_divide_conquer_index, count_index, count_index_method, count_iterator, count_loop,
count_method, count_original, count_sum]
# Only recursive approaches without builtins
# funcs = [count_divide_conquer, count_divide_conquer_index, count_index, count_iterator, count_original]
arguments = {
2**i: MultiArgument(('a', [random.choice(string.ascii_lowercase) for _ in range(2**i)]))
for i in range(1, 12)
}
b = benchmark(funcs, arguments, 'size')
b.plot()
It's log-log scaled to display the range of values in a meaningful way and lower means faster.
One can clearly see that the original approach gets very slow for long inputs (because it copies the list it performs in O(n**2)) while the other approaches behave linearly. What may seem weird is that the divide-and-conquer approaches perform slower, but that is because these need more function calls (and function calls are expensive in Python). However they can process much longer inputs than the iterator and index variants before they hit the recursion limit.
It would be easy to change the divide-and-conquer approach so that it runs faster, a few possibilities that come to mind:
Switch to non-divide-and-conquer when the sequence is short.
Always process one element per function call and only divide the rest of the sequence.
But given that this is probably just an exercise in recursion that goes a bit beyond the scope.
However they all perform much worse than using iterative approaches:
Especially using the count method of lists (but also the one of strings) and the manual iteration are much faster.
The error is because sometimes you just have no return Value. So return 0 at the end of your function fixes this error. There are a lot better ways to do this in python, but I think it is just for training recursive programming.
You are doing things the hard way in my opinion.
You can use Counter from collections to do the same thing.
from collections import Counter
def count(f, s):
if s == None:
return 0
return Counter(s).get(f)
Counter will return a dict object that holds the counts of everything in your s object. Doing .get(f) on the dict object will return the count for the specific item you are searching for. This works on lists of numbers or a string.
If you're bound and determined to do it with recursion, whenever possible I strongly recommend halving the problem rather than whittling it down one-by-one. Halving allows you to deal with much larger cases without running into stack overflow.
def count(f, s):
l = len(s)
if l > 1:
mid = l / 2
return count(f, s[:mid]) + count(f, s[mid:])
elif l == 1 and s[0] == f:
return 1
return 0

Equivalent of `return` for Python generators

Sometimes, when rewriting recursive functions as generators, I miss the brevity of return.
"""
Returns a list of all length n strings that can be made out of a's and/or b's.
"""
def ab_star(n):
if n == 0:
return [""]
results = []
for s in ab_star(n - 1):
results.append("a" + s)
results.append("b" + s)
return results
turns into
"""
Generator for all length n strings that can be made out of a's and/or b's.
"""
def ab_star(n):
if n == 0:
yield ""
else:
for s in ab_star(n - 1):
yield "a" + s
yield "b" + s
It's that else that bugs me. I wish there was a way to say "yield, and this is it, so exit the function". Is there a way?
Don't miss return, use it.
You can return right after you yield.
def ab_star(n):
if n == 0:
yield ""
return
for s in ab_star(n - 1):
yield "a" + s
yield "b" + s
An alternative is to use return in both cases, where the first case returns a sequence of length 1, and the second returns a generator-expression:
def ab_star(n):
if n == 0:
return ( "", )
return ( c+s for s in ab_star(n - 1) for c in 'ab' )
This avoidance of yield avoids the limitation that you cannot use both return <value> and yield in the same function.
(This works in your case because your function doesn't have to be a generator. Since you only iterate over the results, it can also return a tuple.)
There isn't. When I wrote the "Simple Generators PEP", I noted:
Q. Then why not allow an expression on "return" too?
A. Perhaps we will someday. In Icon, "return expr" means both "I'm
done", and "but I have one final useful value to return too, and
this is it". At the start, and in the absence of compelling uses
for "return expr", it's simply cleaner to use "yield" exclusively
for delivering values.
But that never gained traction. Until it does ;-), you can make your generator look more like your first function by writing the first part as:
if n == 0:
yield ""
return
Then you can drop the else: statement and dedent the rest.

Categories