Recursion for generating subsets of a list - python-3.x

I would like to know for this function, why does after it runs and returns [[],[1]] which is the last line of the function? Then it would run the line smaller = genSubset(L[:-1]) again and again. I visualized the code from pythontutor. However, i do not understand why it works this way. Someone please enlighten me. Thank You.
This function generates all the possible subsets of a given list, so, for example, input list is [1,2,3]. It would return [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]].
def genSubsets(L):
res = []
if len(L) == 0:
return [[]] #list of empty list
smaller = genSubsets(L[:-1]) # all subsets without last element
extra = L[-1:] # create a list of just last element
new = []
for small in smaller:
new.append(small+extra) # for all smaller solutions, add one with last element
return smaller+new # combine those with last element and those without
print(genSubsets([1,2,3]))

This function is recursive, that means - it calls itself. when it finishes one recursion iteration - it returns to where it stopped. This is exactly at the line smaller = getSubsets(L[:-1]) but notice that it wont run it again, but instead continue until it finishes the call. This recursion happens n times (where n is the list's length), and you will see this behavior exactly n times before the function finally returns the subsets.
I hope i understood correctly the question and managed to answer it :)

Related

it is give none but ı think the algoritm is true where is the mistake could you tell me?

def listUnder(inList,bound):
a=[]
a.append(inList)
b=[]
b.append(bound)
for i in a:
if i<b:
return i
print(listUnder([34,10,9,5,44,1],10))
Write a function listUnder(inList, bound) which takes two input argumens inlist(list of integers) and bound (int) and returns a list that consists of all elements in inlist that are strictly smaller than bound in the same order they appear in inlist.
output:
print(listUnder([34, 10, 9, 5, 44, 1],10))
[9, 5, 1]
I don't know why you are doing this in a complicated manner, a simple list comp would do the job here:
def listUnder(inList, bound):
return [i for i in inList if i < bound]
Aside from that, appending your input list to a list inside your function results in a nested list, which is probably the source of your error. Also, you return a single list element in your for-loop, how can you expect getting a list?

Is there a way to sort an unsorted list with some repeated elements?

I am trying to sort an unsorted list [4, 5, 9, 9, 0, 1, 8]
The list has two repeated elements. I have tried to approach the question by having a loop that goes through each element comparing each element with the next in the list and then placing the smaller element at the start of the list.
def sort(ls:
ls[x]
x = [4, 5, 9, 9, 0, 1, 8]
while len(x) > 0:
for i in the range(0, len(x)):
lowest = x[i]
ls.append(lowest)
Please, could someone explain where I am going wrong and how the code should work?
It may be that I have incorrectly thought about the problem and my reasoning for how the code should work is not right
I do not know, if this is exactly what you are looking for but try: sorted(ListObject).
sorted() returns the elements of the list from the smallest to the biggest. If one element is repeated, the repeated element is right after the original element. Hope that helped.
Yes, you can try x.sort() or sorted(x). Check this out https://www.programiz.com/python-programming/methods/built-in/sorted. Also, in your program I don't see you making any comparisons, for example, if x[i] <= x[i+1] then ...
This block of code is just gonna append all the elements in the same order, till n*n times.
Also check this https://en.wikipedia.org/wiki/Insertion_sort
For a built-in Python function to sort, let y be your original list, you can use either sorted(y) or y.sort().Keep in mind that sorted(y) will return a new list so you would need to assign it to a variable such as x=sorted(y); whereas if you use x.sort() it will mutate the original list in-place, so you would just call it as is.
If you're looking to actually implement a sorting function, you can try Merge Sort or Quick Sort which run in O (n log n) in which will handle elements with the same value. You can check this out if you want -> https://www.geeksforgeeks.org/python-program-for-merge-sort/ . For an easier to understand sorting algorithm, Insertion or Bubble sort also handle duplicate as well but have a longer runtime O (n^2) -> https://www.geeksforgeeks.org/python-program-for-bubble-sort/ .
But yea, I agree with Nameet, what you've currently posted looks like it would just append in the same order.
Try one of the above suggestions and hopefully this helps point you in the right direction to if you're looking for a built-in function or to implement a sort, which can be done in multiple ways with different adv and disadv to each one. Hope this helps and good luck!
There are several popular ways for sorting. take bubble sort as an example,
def bubbleSort(array):
x = len(array)
while(x > 1): # the code below make sense only there are at least 2 elements in the list
for i in range(x-1): # maximum of i is x-2, the last element in arr is arr[x-1]
if array[i] > array[i+1]:
array[i], array[i+1] = array[i+1], array[i]
x -= 1
return array
x = [4, 5, 9, 9, 0, 1, 8]
bubbleSort(x)
your code has the same logic as below
def sorts(x):
ls = []
while len(x) > 0:
lowest = min(x)
ls.append(lowest)
x.remove(lowest)
return ls
x = [4, 5, 9, 9, 0, 1, 8]
sorts(x)
#output is [0, 1, 4, 5, 8, 9, 9]

Recursion happens too many times and list is not iterable

I'm trying to make a secret santa programm. The input is in form of the list of names of people g. ["John", "Bob", "Alice"] and the list of emials ["John#gmail.com", "Bob#gmail.com", "Alice#outlook.com"]. I need to generate pairs of email adress and a random name which doesn't belong to the said email adress. For this I have written the function compare.
def compare(list_of_names, list_of_emails):
zipped_lists = zip(list_of_emails, list_of_names)
random.shuffle(list_of_emails)
zipped_shuffled_lists = zip(list_of_emails, list_of_names)
for pair in zipped_lists:
for shuffle_pair in zipped_shuffled_lists:
if shuffle_pair == pair:
return compare(list_of_names, list_of_emails)
return zipped_shuffled_lists
But instead of shuffling like it should it just creates a recursion. i still can't find out why. After a finite amount of time it should create two different lists that work. Also the shuffled_list_of_emails is not iterable, why?
EDIT:changed the code with shuffle because it works in place
zip is lazy!
I'm not sure why, but I'm too excited about this right now, so the answer might be a bit messy. Feel free to ask for clarification)
Let's step through your code:
def compare(list_of_names, list_of_emails):
# the `zip` object doesn't actually iterate over any of its arguments until you attempt to iterate over `zipped_lists`
zipped_lists = zip(list_of_emails, list_of_names)
# modify this IN-PLACE; but the `zip` object above has a pointer to this SAME list
random.shuffle(list_of_emails)
# since the very first `zip` object has `list_of_emails` as its argument, AND SO DOES THE ONE BELOW, they both point to the very same, SHUFFLED (!) list
zipped_shuffled_lists = zip(list_of_emails, list_of_names)
# now you're iterating over identical `zip` objects
for pair in zipped_lists:
for shuffle_pair in zipped_shuffled_lists:
# obviously, this is always true
if shuffle_pair == pair:
# say "hello" to infinite recursion, then!
return compare(list_of_names, list_of_emails)
return zipped_shuffled_lists
Let's recreate this in the Python interpreter!
>>> List = list(range(5))
>>> List
[0, 1, 2, 3, 4]
>>> zipped_1 = zip(List, range(5))
>>> import random
>>> random.shuffle(List)
>>> zipped_2 = zip(List, range(5))
>>> print(List)
[4, 2, 3, 0, 1]
>>> zipped_1, zipped_2 = list(zipped_1), list(zipped_2)
>>> zipped_1 == zipped_2
True
You see, two different zip objects applied to the same list at different times (before and after that list is modified in-place) produce the exact same result! Because zip doesn't do the zipping once you do zip(a, b), it will produce the zipped... uh, stuff... on-the-fly, while you're iterating over it!
So, to fix the issue, do not shuffle the original list, shuffle its copy:
list_of_emails_copy = list_of_emails.copy()
random.shuffle(list_of_emails_copy)
zipped_shuffled_lists = zip(list_of_emails_copy, list_of_names)
There's correct answer from #ForceBru already. But a will contribute a little.
You should avoid zip's lazy evaluation and unfold zips with, for example, list:
def compare(list_of_names, list_of_emails):
zipped_lists = list(zip(list_of_emails, list_of_names)) # eager evaluation instead of lazy
random.shuffle(list_of_emails) # shuffle lists
zipped_shuffled_lists = list(zip(list_of_emails, list_of_names)) # eager again
for pair in zipped_lists:
for shuffle_pair in zipped_shuffled_lists:
if shuffle_pair == pair:
return compare(list_of_names, list_of_emails)
return zipped_shuffled_lists
But I guess you need no recursion and can achieve your task easier:
def compare(list_of_names, list_of_emails):
zipped_lists = list(zip(list_of_emails, list_of_names))
random.shuffle(zipped_lists) # shuffle list of emails and names
result = []
shuffled_emails = [i[0] for i in zipped_lists]
for i, _ in enumerate(shuffled_emails):
result.append(zipped_lists[i-1][1]) # shift email relatively one position to the right
return list(zip(result, shuffled_emails))
This code links an name with an email of a previous name, which is randomly selected, and it guaranteed does not match.
There's no recursion, works fine for lists with two or more elements.

Returning a list of lists using python recursion

I'm brushing up on recursion problems and have no issues when the problem statement asks to print values (eg. BST traversal where you print the node values), and similarly few issues when the problem asks to return a list of values (return a path between 2 nodes in a tree) but am having problems when there are multiple answers, involving multiple lists (or a single 2D list) to be returned. For example the problem asks me to return how many ways a child can reach the top of the stairs, assuming it can jump either 1, 2, or 3 steps at a time. This is no problem and is solved below
def step_helper(steps):
if(steps == 0):
return 1
elif(steps < 0):
return 0
else:
return step_helper(steps-1) +
step_helper(steps-2) +
step_helper(steps-3)
def find_num_ways(steps):
count = step_helper(steps)
print(count)
find_num_ways(10)
Similarly if I need to return a path from two nodes in a BST, returning 1 list is no problem
def another_path_helper(self, n1, n2):
if(n1 == None):
return [None]
elif(n1 == n2):
return [n1.data]
elif(n1.data > n2.data):
return [n1.data] + self.another_path_helper(n1.left, n2)
elif(n1.data < n2.data):
return [n1.data] + self.another_path_helper(n1.right, n2)
else:
return None
def another_path(self, n1, n2):
path = self.another_path_helper(n1, n2)
if(None in path):
return None
else:
return path
However, I'm clueless on how I would return a list of lists. In the child-steps example, instead of returning the number of ways a child can climb the stairs, how could I return a list of paths, which would be a 2d list, where each entry would be a list of steps taken to get from bottom to top? Ideally I would not need to pass a list as argument to my recursive function since I've been told passing mutable objects to recursive functions is a bad practice and no different from using a static counter.
There is absolutely nothing wrong with passing a list as an argument to your recursive function, and not modifying it. In fact, doing so makes it fairly trivial to solve the problem.
Consider a small version of the problem: only 3 steps. You are at the bottom of the stairs. You can take one step, two steps or three steps. You then have 3 sub-problems to solve:
All the solutions starting with a path of [1], going 2 additional steps.
All the solutions starting with a path of [2], going 1 additional step.
All the solutions starting with a path of [3], going 0 additional steps.
Looks like a good start towards the recursive solution.
Let's focus on just the first of these sub-problems. Your path is [1], and you have 2 addition steps to go. From here, you can take 1 step, 2 steps or 3 steps. You again have 3 sub-sub-problems:
All the solutions starting with a path of [1,1], going 1 additional step.
All the solutions starting with a path of [1,2], going 0 additional step.
All the solutions starting with a path of [1,3], going -1 additional steps.
The first sub-sub-problem requires more work ... another recursive call, which should return [[1,1,1]]. The second sub-sub-problem should return just the path we've taken to get here: [[1,2]]. And the last sub-sub-problem should return no solutions: []. We add these solutions together [[1,1,1]] + [[1,2]] + [] to get [[1,1,1],[1,2]], and return that.
Backing up, the second sub-problem, "starting with a path of [2], going 1 additional step" should return [[2,1]] as the set of solutions. The third sub-problem, "starting with a path of [3], going 0 additional steps" should return [[3]]. Adding these solutions together with [[1,1,1],[1,2]] gives the complete solution set: [[1,1,1],[1,2],[2,1],[3]]
As code:
def find_paths(total):
def helper(path, remaining):
paths = []
if remaining == 0:
paths.append(path)
elif remaining > 0:
for step in range(1,3+1):
paths.extend( helper(path + [step], remaining - step))
return paths
return helper([], total)
print(find_paths(3))
The output, as expected, is:
[[1, 1, 1], [1, 2], [2, 1], [3]]
Of course, you don't have to pass path, the current list of the steps, into the recursive call. You could instead just ask for all paths from the current step to the top of the stairs, and prefix those with the step just being taken. We don't even need a helper in this case:
def find_paths(remaining):
paths = []
if remaining == 0:
paths.append([])
for step in range(1,3+1):
if step <= remaining:
subpaths = find_paths(remaining - step)
for subpath in subpaths:
paths.append([step] + subpath)
return paths
print(find_paths(4))
The output, as expected, is:
[[1, 1, 1, 1], [1, 1, 2], [1, 2, 1], [1, 3], [2, 1, 1], [2, 2], [3, 1]]
It should be noted that find_paths(2) will be called -- and will return the same subpaths, [[1,1], [2]] -- after ascending the first two steps, either one at a time with the path [1,1] or as one jump of two steps with the path [2]. Since it returns the same value, instead of recomputing all the subpaths from that point, we can cache the result, and reuse the value on subsequent steps.
from functools import lru_cache
#lru_cache()
def find_paths(remaining):
paths = []
if remaining == 0:
paths.append([])
for step in range(1,3+1):
if step <= remaining:
subpaths = find_paths(remaining - step)
for subpath in subpaths:
paths.append([step] + subpath)
return paths
paths = find_paths(10)
print(len(paths))
print(find_paths.cache_info())
274
CacheInfo(hits=17, misses=11, maxsize=128, currsize=11)
If you set the cache size to zero #lru_cache(maxsize=0), you can see that the find_paths() function is called 600 times in the course of the problem: CacheInfo(hits=0, misses=600, maxsize=0, currsize=0). With the cache enabled, it is called only 28 times, and only executes 11 times; 17 times, the previously stored result is immediately returned, which can be a considerable savings.

how do you check if element is already in the list through comprehension?

How do you check whether an element is already in the list when I am doing it in comprehension?
For example say in following comprehension I want to restrict duplicate numbers Though I am not looking for unique number at all, I want to prevent via an if condition.
[x for x in [1,2,3,1,2,3]]
I am looking for something like
[x for x in [1,2,3,1,2,3] if not in self]
I think what you are looking for is set comprehension and conversion to list. It would do what you want without any odd syntax.
ans = list({x for x in [1,2,3,1,2,3])})
Actaully that can be also be simplified to
ans = list(set([1,2,3,1,2,3]))
but I think the first one might be better in performance.
You can't access the comprehension as you're creating it (as far as I know; someone please correct me if I'm wrong!), but in your case you can just use a set, which eliminates duplicates.
uniques = set([1, 2, 3, 1, 2, 3])
print(uniques) # >>> set([1, 2, 3])
If your list comprehension needs to be more complex, you can index the comprehension from the set, rather than the original list.
mylist = [1, 2, 3, 1, 2, 3]
print([x*x for x in set(mylist)]) # >>> [1, 4, 9]
If you really need direct access to the list during creation, you need to use an explicit loop rather than a comprehension.
x = [1,2,3,1,2,3]
y = [x[n] for n in range(len(x)) if x.index(x[n]) == n]
?
I think at this point, it's probably more readable to simply write it using a for-loop rather than a comprehension.

Resources