How can I yield from an arbitrary depth of recursion? - python

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))
]

Related

Python: Passing a list as parameter so the actual list is sorted

I wrote a merge sort function and thought I was done.. but in the assignment it says that the function is supposed to sort the actual list instead of creating a copy (so call by value instead of reference I think?). Right now, it doesn't work because the list itself isn't changed.
def mergeSort(L, ascending = True):
print('Mergesort, Parameter L:')
print(L)
result = []
if len(L) == 1:
return L
mid = len(L) // 2
teilliste1 = mergeSort(L[:mid], ascending)
teilliste2 = mergeSort(L[mid:], ascending)
x, y = 0, 0
while x < len(teilliste1) and y < len(teilliste2):
if (ascending and teilliste1[x] > teilliste2[y]) or (not ascending and teilliste1[x] < teilliste2[y]):
result.append(teilliste2[y])
y = y + 1
else:
result.append(teilliste1[x])
x = x + 1
result = result + teilliste1[x:]
result = result + teilliste2[y:]
return result
liste1 = list([3, 2, -1, 9, 17, 4, 1, 0])
mergeSort(liste1)
print(liste1) # result will be the unsorted list
What do I need to change in the function to make it call by value and sort the actual list?
I know I could do
mergeResult = mergeSort(liste1)
print(mergeResult)
but apparently I have to change the original parameter list.
There are two basic ways to write a recursive decomposition function. The immutable version calls itself on copies of two smaller parts, then reassembles and returns them; that's what you wrote. The mutable version calls itself on the actual input, then modifies that in-place, and returns nothing; that's what your teacher wants here.
Notice that, unlike some other sorting algorithms, mergesort can't be done with constant extra storage, only better than linear extra storage. (Logarithmic is possible, but complicated; I doubt your teacher is insisting on that.) And because of this, most mergesort algorithms you find in books, Wikipedia, etc. will be written as copying sorts rather than in-place sorts. Which means this is probably a bit of a "trick question", trying to see whether you can figure out how to convert from the well-known copying version of the algorithm into an in-place version with explicit extra storage.
You can always write an immutable algorithm and then mutate at the very end, e.g.:
def _mergesort(L, ascending):
# your existing code
def mergesort(L, ascending=True):
L[:] = _mergesort(L, ascending)
This gives you all the cost of immutability without the benefits. But it does mean you can write a variety of sort functions with the same API, which are all implemented in-place if that's a reasonable optimization, but not if it isn't, which seems to be what your teacher is after.
If you don't want a wrapper function, you can change the last line from:
return result
… to:
L[:] = result
However, because this changes the API, you also need to change your recursive calls to match. For example, you could do this:
teilliste1 = L[:mid]
mergeSort(teilliste1, ascending)
teilliste2 = L[mid:]
mergeSort(teilliste2, ascending)
In Python, a mutating recursive decomposition function often works by passing start and end indices down with the list, like this:
def mergesort(L, ascending=True, start=None, stop=None):
if start is None: start = 0
if stop is None: stop = len(L)
if stop - start <= 1:
return
mid = (stop - start) // 2 + start
mergeSort(L[start:mid], ascending)
mergeSort(L[mid:stop], ascending)
# etc.
As mentioned above, the merging step is going to require some auxiliary storage. The simplest thing to do—and probably good enough for your assignment, even though it means linear space—is to just build up a left list and a right list and then assign them back into L[start:mid], L[mid:stop] = left, right.
Notice that this isn't all that different from the L[:] = result version above; it's really just a matter of using L itself, together with start and stop indices, in place of copies for the first half of the process, and then copying only at the end during the merge.

How to return a list that is made up of extracted elements from another list in python?

I am building a function to extract all negatives from a list called xs and I need it to add those extracted numbers into another list called new_home. I have come up with a code that I believe should work, however; it is only showing an empty list.
Example input/output:
xs=[1,2,3,4,0,-1,-2,-3,-4] ---> new_home=[1,2,3,4,0]
Here is my code that returns an empty list:
def extract_negatives(xs):
new_home=[]
for num in range(len(xs)):
if num <0:
new_home= new_home+ xs.pop(num)
return
return new_home
Why not use
[v for v in xs if v >= 0]
def extract_negatives(xs):
new_home=[]
for num in range(len(xs)):
if xs[num] < 0:
new_home.append(xs[num])
return new_home
for your code
But the Chuancong Gao solution is better:
def extract_negative(xs):
return [v for v in xs if v >= 0]
helper function filter could also help. Your function actually is
new_home = filter(lambda x: x>=0, xs)
Inside the loop of your code, the num variable doesn't really store the value of the list as you expect. The loop just iterates for len(xs) times and passes the current iteration number to num variable.
To access the list elements using loop, you should construct loop in a different fashion like this:
for element in list_name:
print element #prints all element.
To achieve your goal, you should do something like this:
another_list=[]
for element in list_name:
if(element<0): #only works for elements less than zero
another_list.append(element) #appends all negative element to another_list
Fortunately (or unfortunately, depending on how you look at it) you aren't examining the numbers in the list (xs[num]), you are examining the indexes (num). This in turn is because as a Python beginner you probably nobody haven't yet learned that there are typically easier ways to iterate over lists in Python.
This is a good (or bad, depending on how you look at it) thing, because had your code taken that branch you would have seen an exception occurring when you attempted to add a number to a list - though I agree the way you attempt it seems natural in English. Lists have an append method to put new elements o the end, and + is reserved for adding two lists together.
Fortunately ignorance is curable. I've recast your code a bit to show you how you might have written it:
def extract_negatives(xs):
out_list = []
for elmt in xs:
if elmt < 0:
out_list.append(elmt)
return out_list
As #ChuangongGoa suggests with his rather terse but correct answer, a list comprehension such as he uses is a much better way to perform simple operations of this type.

How to Sort List Items from Low to High Without Built-In Tools

I'm trying to write this function gensort(list) that takes a list of numbers and returns a new list with the same numbers, but ordered from low to high. An example of the output would be something like
>>> gensort([111, 1, 3.14])
[1, 3.14, 111]
I wrote a function to take one element and return it to its place in ascending oder:
def insert_sorted(elem,list):
if list == []:
return [elem]
elif elem < list[0]:
return [elem] + list
else:
return [list[0]] + insert_sorted(elem, list[1:])
Now I'm trying to apply it to the rest of my list and I came up with this:
def gensort(list):
insert = insert_sorted(list[min],list)
return insert
However, this doesn't work in the least. I'm wondering how I can use insert_sorted recursively, or write a different list comprehension to get it to return the correct order for my whole list.
I know there are built in sorting tools but I'm trying to write this with what I've got currently.
You didn't ask whether creating your own sort function was a good idea, so I'll answer the question you asked, with one way of using insert_sorted to create a full gensort function:
def gensort(list):
sorted_list = []
for item in list:
sorted_list = insert_sorted(item, sorted_list)
return sorted_list
Why not use sort?
If you are putting there numbers only, simple do something like this:
insert_sorted(elem,list):
list.append(elem)
list.sort() // will sort in asc order
This is not a tool, it's standard functionality in python. Your method has big disadvantage, it will be slow and memory requiring on long lists. Better would be add new element to list and run sort function on it.
With your function adding an element to n element array, you will have n+1 function calls, creating n+1 subarrays. thats way to slow, and not acceptable. Use one loop sort algoritm instead, if you don't want to use python sort.
Example of bubble sort in python:
def bubble_sort(list_):
"""Implement bubblesort algorithm: iterate L to R in list, switching values
if Left > Right. Break when no alterations made to to list. """
not_complete = True
while not_complete:
not_complete = False
for val, item in enumerate(list_):
if val == len(list_)-1: val = 0
else:
if list_[val] > list_[val+1]:
list_[val], list_[val+1] = list_[val+1], list_[val]
not_complete = True
return list_

Can Python slicing be used to skip one specific element by index?

Suppose I am writing a recursive function where I want to pass a list to a function with a single element missing, as part of a loop. Here is one possible solution:
def Foo(input):
if len(input) == 0: return
for j in input:
t = input[:]
t.remove(j)
Foo(t)
Is there a way to abuse the slice operator to pass the list minus the element j without explicitly copying the list and removing the item from it?
What about this?
for i in range(len(list_)):
Foo(list_[:i] + list_[i+1:])
You are stilling copying items, though you ignore the element at index i while copying.
BTW, you can always try to avoid overriding built-in names like list by appending underscores.
If your lists are small, I recommend using the approach in the answer from #satoru.
If your lists are very large, and you want to avoid the "churn" of creating and deleting list instances, how about using a generator?
import itertools as it
def skip_i(seq, i):
return it.chain(it.islice(seq, 0, i), it.islice(seq, i+1, None))
This pushes the work of skipping the i'th element down into the C guts of itertools, so this should be faster than writing the equivalent in pure Python.
To do it in pure Python I would suggest writing a generator like this:
def gen_skip_i(seq, i):
for j, x in enumerate(seq):
if i != j:
yield x
EDIT: Here's an improved version of my answer, thanks to #Blckknght in comments below.
import itertools as it
def skip_i(iterable, i):
itr = iter(iterable)
return it.chain(it.islice(itr, 0, i), it.islice(itr, 1, None))
This is a big improvement over my original answer. My original answer only worked properly on indexable things like lists, but this will work correctly for any iterable, including iterators! It makes an explicit iterator from the iterable, then (in a "chain") pulls the first i values, and skips only a single value before pulling all remaining values.
Thank you very much #Blckknght!
Here is a code equivalent to satoru's, but is faster, as it makes one copy of the list per iteration instead of two:
before = []
after = list_[:]
for x in range(0, len(list_)):
v = after.pop(0)
Foo(before + after)
before.append(v)
(11ms instead of 18ms on my computer, for a list generated with list(range(1000)))

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