Recursion and List Mutation - python

I am restrict myself to mutate a list using recursion only, I have a few small troubles when doing so.
def expand_strings(L,s,i):
if len(L) == 0:
return
else:
if len(L[0]) - 1 <= i:
L[0] += s
elif len(L[0]) - 1 > i:
new_string = L[0][:i] + s + L[0][i:]
L[0] = new_string
expand_strings(L[1:], s, i)
return
L: The input list containing possible 1 or more strings
s: The extra portion of string that I need to "insert" or "append" to the string elements within the list
i: The index of string where I want to insert or append s to.
The main goal of this function is the following:
1. if the index i within the range 0 ~ len(string_element_in_list), then I insert my s starting from index i
2. if the index i is larger than what the current string length, then I do the append s.
The problems I am having right now is that: I notice the recursion will only mutate the first element within the input list, and every element after the first element, they won't be affected by the mutation, I figure it might have something to do with the new input list I pass to the recursion, but I don't know precisely why this doesn't work.
Thanks for helping in advance. :)

The problem is in the recursive call expand_strings(L[1:], s, i). When you use slicing to get a part of your list, python creates a whole new copy of that sublist. So the recursive call creates a copy of your list, except the first element, and works on that copy.
One way of solving this problem can be returning the modified list from your method:
def expand_strings(L,s,i):
if len(L) == 0:
return []
else:
if len(L[0]) - 1 <= i:
L[0] += s
elif len(L[0]) - 1 > i:
new_string = L[0][:i] + s + L[0][i:]
L[0] = new_string
return [L[0]] + expand_strings(L[1:], s, i)
If you don't want to create a copy of the sublist every time (and return the modified list), you can add one more parameter to your method that would specify the location of the first element to modify. The base case would be where the starting index is equal to the length of the list.
def expand_strings(L,s,i,start):
if start == len(L):
return
if len(L[start]) - 1 <= i:
L[start] += s
else:
L[start] = L[start][:i] + s + L[start][i:]
expand_strings(L, s, i, start + 1)

Related

"+" vs append in list in python

I am trying to implement bestSum algorithm in python and I have the following script with the issue at line 13,14.
memo = {}
def bestSum(s, l):
if s == 0:
return []
elif s < 0:
return None
elif s in memo:
return memo[s]
minList = None
for i in l:
temp = bestSum(s-i, l)
if temp != None:
temp = temp + [i] #This works LINE13
# temp.append(i) This does not work LINE14
if minList == None or len(temp) < len(minList):
minList = temp
memo[s] = minList
return minList
I know that "+" operator return a new list while the append function mutates the original list.
My question is why does it matter in my code as the temp list is only being used in the next few lines. Why are they giving different outputs.
The problem is not about a difference between append(i) and + [i], it's about how objects work in python. You got close to nailing it down.
First off, minilist = temp makes minilist refer to the same list object temp refers to, so appending to temp also appends to minilist. Meanwhile temp = temp + [i] makes temp a new reference to a new object, while minilist keeps the reference to the original list.
You can see that this is the reason by, for example, in the LINE13 version converting it to += [i], this makes temp keep it's reference thus it would alter both lists. In the LINE14 version you can add temp = temp.copy() before the append to make it a new object.
The difference is that the "+" operation acts specifically when you add an array by concatenating the element. The "append" method appends the object on the right-hand side that you give it, instead of taking the elements.
The + operator is used when you add a list by concatenating the element. The append method, append the item on the right side that you give it, instead of taking its elements, if you want to have a result similar to the + operator try to use extend()

Why does my list of zeroes result in IndexError?

I get an error on occurence[j] = 0. I do not really understand the origins of this error in my code, as it is of length dna, because I append at the top of the code len(dna) zeroes and then I assign some value to the same list occurence in my nested loop, where j can only reach the value of len(dna).
for i in range(len(dna)):
occurence.append(0)
print(f"{len(dna)}")
print(f"{len(occurence)}")
#Calculating consecutive sequences and creating a 2D array occurence...
for i in types:
for j in range(len(dna)):
if (dna[j:j+len(i)] != i):
occurence[j] = 0
else:
space = len(i)
while(dna.find(i, space+len(i)) != -1):
index = dna.find(i, space+len(i))
space = space + len(i)
if (index == len(i)):
occurence[j] += 1
for k in range(len(occurence)):
maximum = 0
if(occurence[k] > maximum):
maximum = occurence[k]
counts.append(maximum)
maximum = 0
occurence.clear()
At the end of the first iteration over types, you call occurence.clear(), which will result in occurence being an empty list. Then, when you try to access occurence[j] on the second iteration, this throws an IndexError since the list is empty.
I think you instead want to initialize your list inside the for i in types loop, e.g.:
for i in types:
occurence = [0] * len(dna)
for j in range(len(dna)):
...
You would then not need to call the clear method on your list, since it would be redefined as a list of zeroes on each iteration.

return a new list that interleaves the two lists but with a twist

def back_interleave(first, second):
if first == [] and second == []:
return []
elif first == []:
return second[::-1]
elif second == []:
return first[::-1]
else:
newlist = []
for i in range(len(first)-1, 0,-1):
newlist.append(first[i])
newlist.append(second[i])
for j in range(len(second)-len(first)-1,0,-1):
newlist.append(second[i])
return newlist
can anybody tells me what's wrong with my code towards this question.
I'm not exactly sure what's wrong with your code, but the second and third if-statements appear to use built-in list reversing functionality which the original problem forbids.
What I would do is determine the length of the longer list, then iterate through both lists backwards.
def back_interleave(first, second):
newlist = []
# You want to iterate through the length of the longer list
length = max(len(first), len(second))
for x in range(length):
# start appending elements from the back of the list
index = -1*(x+1)
if x < len(first):
newlist.append(first[index])
if x < len(second):
newlist.append(second[index])
return newlist
The problem in your code is when you use the range function, the stop value is exclusive i.e., the index 0 is becoming exclusive in your case. And also in the j loop the values at index i are being stored instead of the values at index j.
#CyanideTesla has given the code that works pretty well for your problem

Why my 2nd method is slower than my 1st method?

I was doing leetcode problem No. 387. First Unique Character in a String. Given a string, find the first non-repeating character in it and return it's index. If it doesn't exist, return -1.
Examples:
s = "leetcode"
return 0.
s = "loveleetcode",
return 2.
I wrote 2 algorithm:
Method 1
def firstUniqChar(s):
d = {}
L = len(s)
for i in range(L):
if s[i] not in d:
d[s[i]] = [i]
else:
d[s[i]].append(i)
M = L
for k in d:
if len(d[k])==1:
if d[k][0]<M:
M = d[k][0]
if M<L:
return M
else:
return -1
This is very intuitive, i.e., first create a count dictionary by looping over all the char in s (this can also be done using one line in collections.Counter), then do a second loop only checking those keys whose value is a list of length 1. I think as I did 2 loops, it must have some redundant computation. So I wrote the 2nd algorithm, which I think is better than the 1st one but in the leetcode platform, the 2nd one runs much slower than the 1st one and I couldn't figure out why.
Method 2
def firstUniqChar(s):
d = {}
L = len(s)
A = []
for i in range(L):
if s[i] not in d:
d[s[i]] = i
A.append(i)
else:
try:
A.remove(d[s[i]])
except:
pass
if len(A)==0:
return -1
else:
return A[0]
The 2nd one just loop once for all char in s
Your first solution is O(n), but your second solution is O(n^2), as method A.remove is looping over elements of A.
As others have said - using list.remove is quite expensive... Your use of collections.Counter is a good idea.
You need to scan the string once to find uniques. Then probably what's better is to sequentially scan it again and take the index of the first unique - that makes your potential code:
from collections import Counter
s = "loveleetcode"
# Build a set of unique values
unique = {ch for ch, freq in Counter(s).items() if freq == 1}
# re-iterate over the string until we first find a unique value or
# not - default to -1 if none found
first_index = next((ix for ix, ch in enumerate(s) if ch in unique), -1)
# 2

Python-Merge sort difficulties

I attempted to implement a merge sort, here is my code:
def mergeSort(array):
result=[]
n=len(array)
if n==1:
result=array
else:
a=round(n/2)
first=mergeSort(array[0:a])
second=mergeSort(array[a:n])
for i in range(len(first)):
for j in range(len(second)):
if first[i]<second[j]:
result.append(first[i])
i=i+1
else:
result.append(second[j])
j=j+1
return result
a=[5,4,1,8,7,6,2,3]
b=mergeSort(a)
print(b)
Unfortunately, the result turns out to be [1]. What is wrong with my function?
A number of things...
Firstly, this is a recursive function, meaning you cannot create a list within the function, as you did here:
result=[]
This will simply reset your list after every recursive call, skewing your results. The easiest thing to do is to alter the list that is passed as a parameter to merge sort.
Your next problem is that you have a for loop within a for loop. This will not work because while the first for loop iterates over first, the second for loop will iterate over second for every increment of i, which is not what you want. What you need is to compare both first and second and extract the minimum value, and then the next minimum value, and so on until you get a sorted list.
So your for loops need to be changed to the following:
while i < len(first) and j < len(second):
Which leads me to final problem in your code. The while loop will exit after one of the conditions are met, meaning either i or j (one or the other) will not have reached len(first) or len(second). In other words, there will be one value in either first or second that is unaccounted for. You need to add this unaccounted value to your sorted list, meaning you must implement this final excerpt at the end of your function:
remaining = first if i < j else second
r = i if remaining == first else j
while r < len(remaining):
array[k] = remaining[r]
r = r + 1
k = k + 1
Here r represents the index value where the previous while loop broke off. The while loop will then iterate through the rest of the remaining values; adding them to the end of your sorted list.
You merge sort should now look as follows:
def mergeSort(array):
if len(array)==1:
return array
else:
a=round(len(array)/2)
first=mergeSort(array[:a])
second=mergeSort(array[a:])
i = 0
j = 0
k = 0
while i < len(first) and j < len(second):
if first[i]<second[j]:
array[k] = first[i]
i=i+1
k=k+1
else:
array[k] = second[j]
j=j+1
k=k+1
remaining = first if i < j else second
r = i if remaining == first else j
while r < len(remaining):
array[k] = remaining[r]
r += 1; k += 1
return array
I tried to not alter your code as much as possible in order to make it easier for you to understand. However, if your difficulty in understanding what I did persists, try de-bugging your merge sort using multiple print statements so that you can follow the function's progress and see where it goes wrong.

Categories