Python: how to make a recursive generator function - python

I have been working on generating all possible submodels for a biological problem. I have a working recursion for generating a big list of all the submodels I want. However, the lists get unmanageably large pretty fast (N=12 is just possible in the example below, N>12 uses too much memory). So I wanted to convert it to a generator function using yield instead, but I'm stuck.
My working recursive function looks like this:
def submodel_list(result, pat, current, maxn):
''' result is a list to append to
pat is the current pattern (starts as empty list)
current is the current number of the pattern
maxn is the number of items in the pattern
'''
if pat:
curmax = max(pat)
else:
curmax = 0
for i in range(current):
if i-1 <= curmax:
newpat = pat[:]
newpat.append(i)
if current == maxn:
result.append(newpat)
else:
submodel_generator(result, newpat, current+1, maxn)
result = []
submodel_list(result, [], 1, 5)
That gives me the expected list of submodels for my purposes.
Now, I want to get that same list using a recursion. Naively, I thought I could just switch out my result.append() for a yield function, and the rest would work OK. So I tried this:
def submodel_generator(pat, current, maxn):
'''same as submodel_list but yields instead'''
if pat:
curmax = max(pat)
else:
curmax = 0
for i in range(current):
print i, current, maxn
if i-1 <= curmax:
print curmax
newpat = pat[:]
newpat.append(i)
if current == maxn:
yield newpat
else:
submodel_generator(newpat, current+1, maxn)
b = submodel_generator([], 1, 5)
for model in b: print model
But now I get nothing. A (very dumb) bit of digging tells me the function gets to the final else statement once, then stops - i.e. the recursion no longer works.
Is there a way to turn my first, clunky, list-making function into a nice neat generator function? Is there something silly I've missed here? All help hugely appreciated!

You should change this:
submodel_generator(newpat, current+1, maxn)
to this:
for b in submodel_generator(newpat, current+1, maxn):
yield b
This will recursively yield the value from successive calls to the function.
[Update]: Note that as of Python 3.3, you can use the new yield from syntax:
yield from submodel_generator(newpat, current+1, maxn)

Related

Python: do while loop equivalent using map

Is there a way to continue calling a function only if a condition is met without using loops/recursion/comprehension? I can only use map and filter.
I am bubble sorting a list and I need to reiterate until there is one whole pass without any swap. I use a counter for this.
This is what I have so far:
def sort(l, i, cnt):
if i < len(l) - 1 and l[i] > l[i+1]:
l[i], l[i+1] = l[i+1], l[i]
cnt += 1
return l[i]
def main(l):
cnt = 0
l = list(map(lambda i: sort(l, i, cnt), range(len(l))))
I'm not sure how to continue calling sort only if cnt != 0. Any help is appreciated.
It is un uncommon requirement, but it is possible, provided you are allowed to use other functions to actually execute the iterators. Because both map and filter both only return iterators, so you will have to use sum, list, or tuple for example to actually cause the iterators to return their values.
Here I would use a function that compares 2 consecutive elements in a list, returns 0 in they are in increasing order and swap them and returns 1 if they are not. Using sum on a map on this function will return the number of swaps on a pass:
def sort2(l, i):
if (l[i] > l[i+1]):
l[i], l[i+1] = l[i+1], l[i]
return 1
return 0
You can the execute a pass with:
sum(map(lambda i: sort2(l, i), range(len(l) -1)))
And you execute a full bubble sort by using a second map for all the passes:
sum(map(lambda j: sum(map(lambda i: sort2(l, i), range(len(l) -j))), range(1, len(l))))
In order to stop as soon as one pass resulted in 0 swap, I would filter with a function raising a StopIteration, when it gets 0, because if we use sum, a pass returns 0 as soon as the list is sorted, and the StopIteration will gently stops the iterators:
def stop(x):
# print(x) # uncomment to control the actual calls
if x == 0:
raise StopIteration
return True
Let us combine everything:
tuple(filter(stop, map(lambda j: sum(map(lambda i: sort2(l, i), range(len(l) -j))
), range(1, len(l)))))
Demo:
With l = [1, 3, 5, 2, 4], this gives (with the print uncommented in stop):
2
1
0
(2, 1)
So we have correctly got:
first pass caused 2 swaps ((5,2) and (5,4))
second pass caused 1 swap ((3,2))
third pass caused 0 swap and the stop filter actually stopped the iterators.
That being said, it is a nice exercise, but I would never do that in real world programs...
You might want to check itertools.takewhile; it seems like it is the one you are looking for. See the documentation for itertools for details.
takewhile(lambda x: x<5, [1,4,6,4,1])
# --> 1 4
According to the comments, your goal is to sort a list of numbers between 1 and 50 using map and filter without loops, comprehensions or recursion. Therefore your actual question (about a do/while equivalent for implementing bubble sort) is an XY problem because the correct solution is not to use bubble sort at all. When asking questions in future, please say what your actual problem is - don't just ask how to fix your attempted solution.
The counting sort algorithm is suitable here; something akin to counting sort can be implemented using map and filter without side-effects:
def counting_sort(numbers):
min_x, max_x = min(numbers), max(numbers)
r_x = range(min_x, max_x + 1)
r_i = range(len(numbers))
count_ge = list(map(sum, map(lambda x: map(x.__ge__, numbers), r_x)))
return list(map(lambda i: min_x + sum(map(i.__ge__, count_ge)), r_i))
Note that this is a very inefficient variant of counting sort; it runs in O(nk) time instead of O(n + k) time. But it meets the requirements.

How can I yield from an arbitrary depth of recursion?

I've written a function to create combinations of inputs of an arbitrary length, so recursion seemed to be the obvious way to do it. While it's OK for a small toy example to return a list of the results, I'd like to yield them instead. I've read about yield from, but don't fully understand how it is used, the examples don't appear to cover my use case, and hoping'n'poking it into my code has not yet produced anything that works. Note that writing this recursive code was at the limit of my python ability, hence the copious debug print statements.
This is the working list return code, with my hopeful non-working yield commented out.
def allposs(elements, output_length):
"""
return all zero insertion paddings of elements up to output_length maintaining order
elements - an iterable of length >= 1
output_length >= len(elements)
for instance allposs((3,1), 4) returns
[[3,1,0,0], [3,0,1,0], [3,0,0,1], [0,3,1,0], [0,3,0,1], [0,0,3,1]]
"""
output_list = []
def place_nth_element(nth, start_at, output_so_far):
# print('entering place_nth_element with nth =', nth,
# ', start_at =', start_at,
# ', output_so_far =', output_so_far)
last_pos = output_length - len(elements) + nth
# print('iterating over range',start_at, 'to', last_pos+1)
for pos in range(start_at, last_pos+1):
output = list(output_so_far)
# print('placing', elements[nth], 'at position', pos)
output[pos] = elements[nth]
if nth == len(elements)-1:
# print('appending output', output)
output_list.append(output)
# yield output
else:
# print('making recursive call')
place_nth_element(nth+1, pos+1, output)
place_nth_element(0, 0, [0]*output_length)
return output_list
if __name__=='__main__':
for q in allposs((3,1), 4):
print(q)
What is the syntax to use yield from to get my list generated a combination at a time?
Recursive generators are a powerful tool and I'm glad you're putting in the effort to study them.
What is the syntax to use yield from to get my list generated a combination at a time?
You put yield from in front of the expression from which results should be yielded; in your case, the recursive call. Thus: yield from place_nth_element(nth+1, pos+1, output). The idea is that each result from the recursively-called generator is iterated over (behind the scenes) and yielded at this point in the process.
Note that for this to work:
You need to yield the individual results at the base level of the recursion
To "collect" the results from the resulting generator, you need to iterate over the result from the top-level call. Fortunately, iteration is built-in in a lot of places; for example, you can just call list and it will iterate for you.
Rather than nesting the recursive generator inside a wrapper function, I prefer to write it as a separate helper function. Since there is no longer a need to access output_list from the recursion, there is no need to form a closure; and flat is better than nested as they say. This does, however, mean that we need to pass elements through the recursion. We don't need to pass output_length because we can recompute it (the length of output_so_far is constant across the recursion).
Also, I find it's helpful, when doing these sorts of algorithms, to think as functionally as possible (in the paradigm sense - i.e., avoid side effects and mutability, and proceed by creating new objects). You had a workable approach using list to make copies (although it is clearer to use the .copy method), but I think there's a cleaner way, as shown below.
All this advice leads us to:
def place_nth_element(elements, nth, start_at, output_so_far):
last_pos = len(output_so_far) - len(elements) + nth
for pos in range(start_at, last_pos+1):
output = output_so_far[:pos] + (elements[nth],) + output_so_far[pos+1:]
if nth == len(elements)-1:
yield output
else:
yield from place_nth_element(elements, nth+1, pos+1, output)
def allposs(elements, output_length):
return list(place_nth_element(elements, 0, 0, (0,)*output_length))
HOWEVER, I would not solve the problem that way - because the standard library already offers a neat solution: we can find the itertools.combinations of indices where a value should go, and then insert them. Now that we no longer have to think recursively, we can go ahead and mutate values :)
from itertools import combinations
def place_values(positions, values, size):
result = [0] * size
for position, value in zip(positions, values):
result[position] = value
return tuple(result)
def possibilities(values, size):
return [
place_values(positions, values, size)
for positions in combinations(range(size), len(values))
]

How to return a list of vertices that have at minimum n edges from an adjacency list in python?

I want to write a function friend(my_class, n) that return a list of people who have minimum n friends, return an empty list if no person has at least n friends.
This my code, can someone tell me what i did wrong?
my_class = [[1,2,3],[0,3],[0,4],[0,1],[2]]
def friend(my_class,n):
for j in range(len(my_class)):
if my_class[j][n] >= n:
return my_class[j]
return ([])
because when i call:
friend(my_class, 2)
it returns [1,2,3] instead of [0,1,2,3]
This is basic debugging, which you need to learn to do for yourself.
my_class = [[1,2,3],[0,3],[0,4],[0,1],[2]]
def friend(my_class,n):
for j in range(len(my_class)):
if my_class[j][n] >= n:
return my_class[j]
return ([])
friend(my_class, 2) will enter a for loop. In the case where j is 0, we look at my_class[0] and there are 3 friends. Because 3>2 we immediately return. Once the function executes a return, it is DONE. It will not continue in the for loop.
You found the adjacency list for the first node which has at least 2 friends, and that's what you returned. I can't go any further without doing your homework for you, but in answer to your question, "What did you do wrong?" you executed return (on the wrong data) before the function was done.

Sum nested lists with recursive function

Hi i have a nested list which i should sum it from recursive function
how can i do that?
my solution didn't work
def n_l_sum(n):
s=0
for i in n:
if i==list:
s+=i
else:
s+=n_l_s(n)
return s
use the isinstanceof function instead of using the ==
def summer(lst):
if not isinstance(lst, list) : return lst
sum = 0
for x in lst:
sum += summer(x)
return sum
def main():
a= [1,2,3, [5,6]]
print(summer(a))
Recursion can be quite difficult to grasp at first; because it can be very intuitive to conceptualize but an pain to implement. I will try to explain my answer as thoroughly as possible so that you understand what exactly is happening:
def n_l_sum(n,i):
s=0 #somewhere to store our sum
lenLst= len(n)
cur = n[i] #sets current element to 'cur'
if lenLst-1-i < 1: #this is the base case
return cur
else:
s = n_l_sum(n,i+1)+cur #where the actual iteration happens
print(s) #give result
return s
n=[6,4]
n_l_sum(n,0)
The Base Case
The first important thing to understand is the base case this gives the recursion a way of stopping.
For example without it the function would return an IndexError because eventually n_l_sum(n,i+1) would eventually go over the maximum index. Therefore lenLst-1-i < 1 stops this; what it is saying: if there is only one element left do this.
Understanding Recursion
Next, I like to think of recursion as a mining drill that goes deep into the ground and collects its bounty back on the way up to the surface. This brings us to stack frames you can think of these as depths into the ground (0-100 m, 100-200 m). In terms of programming they are literal frames which store different instances of variables. In this example cur will be 4 in frame 1 then 6 in frame 2.
once we hit the base case we go back up through the frames this is where s = n_l_sum(n,i+1)+cur does the legwork and calculates the sum for us.
If you are still struggling to understand recursion try and run this code through http://www.pythontutor.com/visualize.html#mode=edit to see what is happening on a variable level.
def n_l_s(n):
s=0
for i in n:
if type(i) != type([]):
s+=i
else:
s+=n_l_s(i)
return s
n=[3, -1, [2, -2], [6, -3, [4, -4]]]
print(n_l_s(n))
output:
5
The functional way would be to to this:
def list_sum(l):
if not isinstance(l, list): # recursion stop, l is a number
return l
# do list_sum over each element of l, sum result
return sum(map(list_sum, l))

Function that tests another function for all numbers 1 to 1000

I've created a function named number(x) that tests a number to see whether or not a number is perfect or not. Now my goal is to create a tester function that tests all numbers from 1 to 1000 and return numbers that are perfect. This is the code i have for the test function:
def unittest():
for i in range(0,1000):
perfect(i)
if True:
return i
It's not working, but i think i'm close. Any advice or help?
I think you meant this, and notice the correct parameters for range, and how we use a list to accumulate all the results - otherwise, the function will return only one value!
def unittest():
ans = []
for i in range(1, 1001):
if perfect(i):
ans.append(i)
return ans
Alternatively, and not recommended (it's redundant), you could test if the returned value was True:
def unittest():
ans = []
for i in range(1, 1001):
if perfect(i) is True :
ans.append(i)
return ans
Yet another alternative would be to use list comprehensions, which is more idiomatic and potentially faster than using an explicit loop:
def unittest():
return [i for i in range(1, 1001) if perfect(i)]
When you return, that's the end of your function. If you want to return all of the perfect numbers, you have to keep track of them and return them all at the end.
On top of that, your if True: means you'll return 0 whether it's perfect or not.
So, what you need to do is:
def unittest():
results = []
for i in range(1000):
if perfect(i):
results.append(i)
return results
There actually is a way to solve this without building the list, by using yield instead of return. That's probably too advanced for you to learn right now, but I'll explain it anyway. First, here's the code:
def unittest():
for i in range(1000):
if perfect(i):
yield i
See the tutorial section on Iterators, and the following two sections, for details. But basically, a yield is like a return that doesn't return. What your function actually returns is not a list, but a generator. If someone then iterates over that generator, it will go through your function until the first yield, then go through until the next yield, and so on until you're done. The tutorial explains this much better than a single paragraph ever could.

Categories