how to fix the sort function - python

def merge (l1,l2):
if l1 and l2:
if l1 == [] and l2 == []:
return []
if l1[0] > l2[0]:
l1, l2 = l2, l1
return [l1[0]] + merge(l1[1:], l2)
return l1 + l2
def sort(l):
x = len(l) / 2
x = int(x)
y = merge(l[0:x], l[x+1:])
return y
I need to write a recursive function named sort; it is passed any unordered list (all int or all str) and it returns a new list that contains every value from its argument list, but in sorted/non-descending order. But I cannot call any of Python’s functions/methods that perform sorting.
also, For any list that has at least 2 values, I have to break the list in half and recursively call sort to sort each smaller list, I have to use the merge function, written above, to merge these two sorted lists returned from these recursive calls
merge is a function to combine and sort two list
merge([1,3,5,8,12],[2,3,6,7,10,15]) returns [1,2,3,3,5,6,7,8,10,12,15].
For example, calling sort([4,5,3,1,6,7,2]) would call sort recursively on the lists [4,5,3] and [1,6,7,2]), returning the lists [3,4,5] and [1,2,6,7] respectively, which when merged would return the list [1,2,3,4,5,6,7].
My function got the following error
39 *Error: sort([1,2,3,4,5,6,7]) -> [1, 2, 3, 5, 6, 7] but should -> [1, 2, 3, 4, 5, 6, 7]
40 *Error: sort([7,6,5,4,3,2,1]) -> [3, 2, 1, 7, 6, 5] but should -> [1, 2, 3, 4, 5, 6, 7]
41 *Error: sort([4,5,3,1,2,7,6]) -> [2, 4, 5, 3, 7, 6] but should -> [1, 2, 3, 4, 5, 6, 7]
42 *Error: sort([1,7,2,6,3,5,4]) -> [1, 3, 5, 4, 7, 2] but should -> [1, 2, 3, 4, 5, 6, 7]
What is wrong with me sort method? can someone help me to fix it? thanks in advance.

Three problems:
Your y = merge(l[0:x], l[x+1:]) loses l[x], make it y = merge(l[:x], l[x:]).
It doesn't sort the halves, so make it y = merge(sort(l[:x]), sort(l[x:])).
You have no base case, stopping the recursion when there's nothing to do.
Corrected and simplified a bit:
def sort(l):
if len(l) <= 1:
return l
x = len(l) // 2
return merge(sort(l[:x]), sort(l[x:]))
The // is integer division so you don't need the extra int(...). And no point in creating that y variable.
Btw, the if l1 == [] and l2 == []: test inside if l1 and l2: is pointless (if l1 and l2 were [], you wouldn't get inside the if l1 and l2: block in the first place), so you can remove it.
One more thing: While your merge function isn't wrong, it's slow. Every l1[1:] takes time proportional to the length of l1. You'd better uses indexes, like for example in Huy Vo's answer.

Ok basically everything you're doing is redundant.
list1 = [1,3,5,8,12]
list2 = [2,3,6,7,10,15]
list3 = list1 + list2 # Merges lists
list3_sorted = sorted(list3) # Sorts them
Also a little bonus, if you have a list of lists or tuples and you want to sort by an index of each of those
from operator import itemgetter
list = [(2,6), (3,4)]
list_sorted = sorted( list, key=itemgetter(1) ) # Will sort by index 1 of each item.
Edit: I now realise that you can't use any built in functions, give me a little to mess around and see if I can figure something out

What you need is a merge sort, I believe there are multiple merge sort pseudocodes on the internet.
Anyway, here is a version of mine in Python 3:
def mergesort(lst):
if len(lst) < 2:
return lst
else:
middle = len(lst) // 2
# recursion, baby
left_half = mergesort(lst[:middle])
right_half = mergesort(lst[middle:])
return merge(left_half, right_half)
def merge(left, right):
result = []
i, j = 0, 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
elif left[i] > right[j]:
result.append(right[j])
j += 1
result += left[i:] + right[j:]
return result

Related

(Python)How to merge 3 decreasing list into one decreasing list?

Using Python, write a function, which takes three lists as input: left, middle and right. These lists will already have been sorted into decreasing order. Merge these three lists into one list, with elements sorted from largest to smallest. Return the resulting list. Not allowed to use sort().
def Merge3Way(left,middle,right):
"""Takes three lists that are sorted in decreasing order and merges them into
one list ordered largest to smallest"""
res = []
while len(left) > 0 and len(right)> 0 and len(middle)>0:
if left[0] > right[0] and left[0] > middle[0]:
res.append(left.pop(0))
elif middle[0] > right[0] and middle[0] > left[0]:
res.append(middle.pop(0))
else:
res.append(right.pop(0))
if left:
res.extend(left)
if middle:
res.extend(middle)
if right:
res.extend(right)
return res
This is the code that I have made.
But when the input is [3,2,1],[6,5,4],[9,8,7], the result is not correct.
What’s the problem in my code? Or any other ways to make the function?
This should help you!
def Merge3Way(left, middle, right):
"""Takes three lists that are sorted in decreasing order and merges them into
one list ordered largest to smallest"""
# We get one big list, for example [1, 2] + [3, 4] = [1, 2, 3, 4]
L = left + middle + right
result = []
while len(L) > 0:
el = L[0]
# by default, we think that it should be inserted at the end of
index = len(result)
# if there is a smaller item in the list, then insert before it
for k in range(len(result)):
if el > result[k]:
index = k
break
result.insert(index, el)
del L[0]
return result
l1 = [3,2,1]
l2 = [6,5,4]
l3 = [9,8,7]
Merge3Way(l1, l2, l3)
# [9, 8, 7, 6, 5, 4, 3, 2, 1]
did not initially see what was necessary in the order of decreasing =)
Following code based upon:
1st merging left and right to get an intermediate result
Next merging intermediate result with middle
Avoid use of lst.pop(0) since this i an expensive operation i.e. O(len(lst))
Code
def Merge3Way(left,middle,right):
"""Takes three lists that are sorted in decreasing order and merges them into
one list ordered largest to smallest"""
# Step 1: Merge left & right
res_1 = []
left_ind, right_ind = 0, 0
while left_ind < len(left) and right_ind < len(right):
if left[left_ind] > right[right_ind]:
res_1.append(left[left_ind])
left_ind += 1
else:
res_1.append(right[right_ind])
right_ind += 1
for left_ind in range(left_ind, len(left)):
res_1.append(left[left_ind])
for right_ind in range(right_ind, len(right)):
res_1.append(left[right_index])
# Step 2: Merge intermediate result and middle
res_2 = []
res_ind, middle_ind = 0, 0
while res_ind < len(res_1) and middle_ind < len(middle):
if res_1[res_ind] > middle[middle_ind]:
res_2.append(res_1[res_ind])
res_ind += 1
else:
res_2.append(middle[middle_ind])
middle_ind += 1
for res_ind in range(res_ind, len(res_1)):
res_2.append(res_1[res_ind])
for middle_index in range(middle_ind, len(middle)):
res_2.append(middle[middle_ind])
return res_2
Test
result = Merge3Way([3,2,1],[6,5,4],[9,8,7])
print(result)
# Output: [9, 8, 7, 6, 5, 4, 3, 2, 1]
You could use a dict to manage dups and take the repetitive max of the keys:
def merge(l,m,r):
rtr=[]
cnt={}
# first count the entries in all three lists as merged:
for x in l+m+r:
cnt[x]=cnt.get(x,0)+1
# now take the max of those entries and delete one by one
# exit when all entries of cnt have been used
while cnt:
x=max(cnt.keys())
if cnt[x]>1:
cnt[x]=cnt[x]-1
else:
del cnt[x]
rtr.append(x)
return rtr
Test it:
>>> merge([3,2,1],[6,5,4],[9,8,7])
[9, 8, 7, 6, 5, 4, 3, 2, 1]
>>> merge([3,2,1],[6,5,3],[9,8,7])
[9, 8, 7, 6, 5, 3, 3, 2, 1]
This method handles lists that are not the same length as well:
>>> merge([3,2,1],[6,5,4],[9,8,7,5,0])
[9, 8, 7, 6, 5, 5, 4, 3, 2, 1, 0]
Or if the sublists are not sorted at all:
>>> merge([3,2,1],[6,5,4],[9,8,7])
[9, 8, 7, 6, 5, 4, 3, 2, 1]
The statement that each of the three list is already sorted is somewhat misleading; you still have to reorder the entire merged list since the lists may have duplicates or interweaving values. It is faster and easier to ignore the sorting of the three lists and just concentrate on the most efficient way to order the combination of the three.

Sort tree which divides list into k parts python

I understand how to sort a list using a binary tree. Eg. sort [ 1,3,5,6,7,3,4,2] from smallest to largest. I recursively split the data into 2 parts each time until it becomes n lists. I then compare 2 lists at a time and append the smaller value into a new list. I do not understand how to do this when it requiress me to splits a list into k parts each time. Eg. k=3. [1,3,5] [6,7,3] [4,2] .I could only find a solution in Java so could someone explain this to me using python?
You have k sublists. At every iteration, find the sublist whose first element is the smallest; append that element to the result list; advance one in that sublist and don't advance in the other sublists.
This is easier if you have a function arg_min or min_with_index that gives you the smallest element as well as its index (so you know which sublist it comes from).
Here are two equivalent ways of writing function min_with_index using python's builtin min to get the min, and enumerate to get the index:
def min_with_index(it):
return min(enumerate(it), key=lambda p:p[1])
import operator
def min_with_index(it):
return min(enumerate(it), key=operator.itemgetter(1))
# >>> min_with_index([14,16,13,15])
# (2, 13)
This was for merging. Here are two different ways of splitting, using list slices:
def split_kway_1(l, k):
return [l[i::k] for i in range(k)]
def split_kway_2(l, k):
j = (len(l)-1) // k + 1
return [l[i:i+j] for i in range(0,len(l),j)]
def split_kway_3(l, k):
j = len(l) // k
result = [l[i:i+j] for i in range(0, j*(k-1), j)]
result.append(l[j*(k-1):])
return result
# >>> split_kway_1(list(range(10)), 3)
# [[0, 3, 6, 9], [1, 4, 7], [2, 5, 8]]
# >>> split_kway_2(list(range(10)), 3)
# [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9]]
# >>> split_kway_3(list(range(10)), 3)
# [[0, 1, 2], [3, 4, 5], [6, 7, 8, 9]]
# versions 2 and 3 differ only when the length of the list is not a multiple of k
And now we can combine splitting and merging to write merge sort:
import operator
def split_kway(l, k):
return [l[i::k] for i in range(k)]
def min_with_index(it):
return min(enumerate(it), key=operator.itemgetter(1))
def merge_kway(list_of_sublists):
result = []
list_of_sublists = [l for l in list_of_sublists if len(l) > 0]
while list_of_sublists:
i,v = min_with_index(l[0] for l in list_of_sublists)
result.append(v)
if len(list_of_sublists[i]) > 1:
list_of_sublists[i].pop(0) # advance in sublist i
else:
list_of_sublists.pop(i) # remove sublist i which is now empty
return result
def merge_sort_kway(l, k):
if len(l) > 1:
list_of_sublists = split_kway(l, k)
list_of_sublists = [merge_sort_kway(l, k) for l in list_of_sublists]
return merge_kway(list_of_sublists)
else:
return list(l)
See also: Wikipedia on k-way merge

How can I use recursion to add two lists together?

For example, say I have
a = [1, 3, 9]
b = [1, 2, 4, 5]
and I want to return a new list(n) in sequential order:
n = [1,1,2,3,4,5,9] by using recursion.
I have tried doing this using a standard recursion method but my issue is that whenever I create a new list inside the recursive function, its value is always set to [] again during the recursive call. Is there any way to have n not go back to being empty?
def recur(a,b):
if a[0] <= b[0]:
n.append(a[0])
return (merge(a[1:], b))
One way (off the top of my head, may have mistakes):
def merge_sorted(a, b):
if not a:
return b
if not b
return a
if a[0] < b[0]:
return [a[0]] + merge_sorted(a[1:], b)
return [b[0]] + merge_sorted(a, b[1:])
I suggest implementing a few versions, and try finding some yourself, if learning recursion is your goal. One version could be similar to what you started - returning the result in a 3d list (so you would need a 3d argument. The other option is a global which i don't like personally). The recipe for me is always the same:
Define the stop condition. In the above, obviously if one of the inputs is empty, and the other is sorted, than the other one is indeed the result.
From that infer the returning invariant - in the above case is that the result of merge_sorted returns a proper merger of the lists.
Given the invariant, construct the recursive call reducing the size of the input.
This is very similar to proof by induction (identical?).
You can do this:
def recur(array):
ret = []
if len(array) <= 1:
return array;
half = int(len(array) / 2)
lower = recur(array[:half])
upper = recur(array[half:])
lower_len = len(lower)
upper_len = len(upper)
i = 0
j = 0
while i != lower_len or j != upper_len:
if( i != lower_len and (j == upper_len or lower[i] < upper[j])):
ret.append(lower[i])
i += 1
else:
ret.append(upper[j])
j += 1
return ret
a = [1, 3, 9]
b = [1, 2, 4, 5]
print(a+b) #[1, 3, 9, 1, 2, 4, 5]
print(recur(a+b)) #[1, 1, 2, 3, 4, 5, 9]
a = [1, 3, 9]
b = [1, 2, 4, 5]
c=[]
def recurse(a,b,c):
if len(a)==0 and len(b)==0:
pass
elif len(a)==0 and len(b)!=0:
c.extend(b)
elif len(a)!=0 and len(b)==0:
c.extend(a)
elif a[0]<b[0]:
c.append(a[0])
del a[0]
recurse(a,b,c)
else:
c.append(b[0])
del b[0]
recurse(a,b,c)
recurse(a,b,c)
Explanation:
1.Create an empty list c
2.1st If: if both 'a' & 'b' are empty, your recursion is complete
3.1st elif:If only 'a' is empty, extend() all value of 'b' to 'c' & recursion complete
4.2nd elif: similar to 1st elif but vice versa
5.If both are non-empty, check for 1st element of 'a' & 'b'. Whichever is lower, append to 'c' & delete, recursively call 'recurse'
Though it considers both 'a' & 'b' are sorted as in the example above

Unable to reset counters in for loop

I am trying to amend a list of integers in a way that every 2 duplicating integers will be multiplied by 2 and will replace the duplicates. here is an example:
a = [1, 1, 2, 3] = [2, 2 ,3] = [4 ,3]
also : b = [2, 3, 3, 6 ,9] = [2 , 6 , 6, 9] = [2, 12 , 9]
I am using the code below to achieve this. Unfortunately, every time I find a match my index would skip the next match.
user_input = [int(a) for a in input().split()]
for index, item in enumerate(user_input):
while len(user_input)-2 >= index:
if item == user_input[index + 1]:
del user_input[index]
del user_input[index]
item += item
user_input.insert(index,item)
break
print(*user_input)
In Python, you should never modify a container object while you are iterating over it. There are some exceptions if you know what you are doing, but you certainly should not change the size of the container object. That is what you are trying to do and that is why it fails.
Instead, use a different approach. Iterate over the list but construct a new list. Modify that new list as needed. Here is code that does what you want. This builds a new list named new_list and either changes the last item(s) in that list or appends a new item. The original list is never changed.
user_input = [int(a) for a in input().split()]
new_list = []
for item in user_input:
while new_list and (item == new_list[-1]):
new_list.pop()
item *= 2
new_list.append(item)
print(*new_list)
This code passes the two examples you gave. It also passes the example [8, 4, 2, 1, 1, 7] which should result in [16, 7]. My previous version did not pass that last test but this new version does.
Check if this works Rory!
import copy
user_input = [1,1,2,3]
res = []
while res!=user_input:
a = user_input.pop(0)
if len(user_input)!=0
b = user_input.pop(0)
if a==b:
user_input.insert(0,a+b)
else:
res.append(a)
user_input.insert(0,b)
else:
res.append(a)
user_input = copy.deepcopy(res)
You can use itertools.groupby and a recursion:
Check for same consecutive elements:
def same_consec(lst):
return any(len(list(g)) > 1 for _, g in groupby(lst))
Replace consecutive same elements:
def replace_consec(lst):
if same_consec(lst):
lst = [k * 2 if len(list(g)) > 1 else k for k, g in groupby(lst)]
return replace_consec(lst)
else:
return lst
Usage:
>>> a = [8, 4, 2, 1, 1, 7]
>>> replace_consec(a)
[16, 7]

How do I stop the function when I have a unique list?

I tried a function that would remove both adjacent duplicates in a list. The remove any new duplicate pair and the function will keep going until there are no more duplicate pairs in the list.
I ran into the issue of figuring out how to tell the function to stop once I have a list without adjacent duplicates.
def removepair(no):
i = 1
if len(no) == 0 or len(no) == 1:
return no
while i < len(no):
if no[i] == no[i-1]:
no.pop(i)
no.pop(i-1)
i -= 1
i += 1
return removepair(no)
So far the function will return 0 or single elements after removal:
input: [1, 2, 2, 1] output: []
or
input: [4, 4, 4, 4, 4] output: [4]
But the problem is I don't know how to stop the recursive function once it has a list with more than 1 element:
input: [1,2,3,3,2,1,5,6,7]
expected output: [5,6,7]
We may be able to avoid boolean flags and counters if we set up our recursion carefully:
def removepairs(numbers):
if not numbers: # base case #1, empty
return numbers
first, *second_on = numbers
if not second_on: # base case #2, one element
return numbers
second, *third_on = second_on
if first == second:
return removepairs(third_on)
result = [first] + removepairs(second_on)
if result == numbers:
return numbers # base case #3, no change!
return removepairs(result)
print(removepairs([1, 2, 3, 3, 2, 1, 5, 6, 7]))
OUTPUT
> python3 test.py
[5, 6, 7]
>
If recursive function is not a requirement, it can be simply done using the following code. I have commented the print statement.
def removepair(input_list):
unique_input_list = list(set(input_list))
output_list = list(x for x in unique_input_list if input_list.count(x)%2 == 1)
#print('Input List: ', input_list)
#print('Output list: ', output_list)
return output_list
Input List: [1, 2, 3, 3, 2, 1, 5, 6, 7]
Output list: [5, 6, 7]
Input List: [4, 4, 4, 4, 4]
Output list: [4]
Input List: [1, 2, 3, 3, 2, 1]
Output list: []
Your recursion should stop when no elements where popped from the list, not when the list is almost empty:
def removepair(no):
L = len(no)
if L <= 1:
return no
i = 1
while i < len(no):
if no[i] == no[i-1]:
no.pop(i)
no.pop(i-1)
i -= 1
i += 1
if len(no) < L:
# elements where popped since the list len has decreased
return removepair(no)
else:
return no
Your code is difficult to understand since it uses a mix of recursion and side effects. Usually, you use either one or the other. Here you can replace your recursive call with a while:
def removepair(no):
while True:
L = len(no)
if L <= 1:
return no
i = 1
while i < len(no):
if no[i] == no[i-1]:
no.pop(i)
no.pop(i-1)
i -= 1
i += 1
if len(no) == L: # no elements where popped
return no
But it's not really Pythonic and I think you should not modify the parameter no inside the function but rather return a new list. Why not iterate over the list and do not copy the duplicates in the result?
def removepair(no):
ret = []
for e in no:
if ret and e == ret[-1]: # current element is the same as the last element
ret.pop()
else:
ret.append(e)
return ret
Or with a fold:
def removepair(no):
import functools
return functools.reduce(lambda acc, x: acc[:-1] if acc and acc[-1]==x else acc+[x], no, [])

Categories

Resources