I'm trying to implement a merge sort in Python. I completed a merge sort lesson on Khan Academy where they had me implement it in JavaScript, but I wanted to try and implement it in Python.
Lesson: https://www.khanacademy.org/computing/computer-science/algorithms#merge-sort
Here is my code:
from math import floor
def merge(array, p, q, r):
left_array = []
right_array = []
k = p
while (k < q):
left_array.append(array[k])
k += 1
while (k < r):
right_array.append(array[k])
k += 1
k = p
i = 0
j = 0
while (i < len(left_array) and j < len(right_array)):
if (left_array[i] <= right_array[j]):
array[k] = left_array[i]
k += 1
i += 1
else:
array[k] = right_array[j]
k += 1
j += 1
while (i < len(left_array)):
array[k] = left_array[i]
k += 1
i += 1
while (j < len(right_array)):
array[k] = right_array[j]
k += 1
j += 1
print("Merging", array)
def merge_sort(array, p, r):
print("Splitting", array)
if p < r:
q = floor((p + r) / 2)
merge_sort(array, p, q)
merge_sort(array, q + 1, r)
merge(array, p, q, r)
test3 = [3, 2, 1]
merge_sort(test3, 0, len(test3))
There's a bug somewhere in my code and I can't seem to get it. I think that it has to do with my splicing, but I haven't been able to confirm this. Here is my output for the test at the bottom:
Splitting [3, 2, 1]
Splitting [3, 2, 1]
Splitting [3, 2, 1]
Splitting [3, 2, 1]
Merging [3, 2, 1]
Splitting [3, 2, 1]
Splitting [3, 2, 1]
Splitting [3, 2, 1]
Merging [3, 2, 1]
Merging [2, 1, 3]
I took the idea of adding print statements from here.
Any help is appreciated. Thank you!
Your code is not following the conventions of the text you linked to on whether the bounds are exclusive or inclusive. In the text, they are inclusive, but in your code they are exclusive of the upper bound. As a result, when you have these two lines:
merge_sort(array, p, q)
merge_sort(array, q + 1, r)
the first sorts array[p] through array[q-1], the second sorts array[q+1] through array[r-1], and you end up completely skipping array[q].
I think you will find it easier to follow the conventions of the text and make both bounds inclusive. So modify you code, start with
test3 = [3, 2, 1]
merge_sort(test3, 0, len(test3) - 1)
, and go from there.
You can also clean up your code greatly by using python slice notation. For example:
left_array = []
right_array = []
k = p
while (k < q):
left_array.append(array[k])
k += 1
while (k < r):
right_array.append(array[k])
k += 1
can be simplified to
left_array = array[p:q]
right_array = array[q:r]
although, as I stated, you'll probably want to start using inclusive indices.
Related
I am learning to tackle a similar type of dynamic programming problem to find a maximum path sum in a matrix.
I have based my learning on this algorithm on the website below.
Source: Maximum path sum in matrix
The problem I am trying to solve is a little bit different from the one on the website.
The algorithm from the website makes use of max() to update values in the matrix to find max values to create a max path.
For example, given an array:
sample = [[110, 111, 108, 1],
[9, 8, 7, 2],
[4, 5, 10, 300],
[1, 2, 3, 4]]
The best sum path is 111 + 7 + 300 + 4 = 422
In the example above, the algorithm finds the first path by finding what is the max value of the first row of the matrix.
My question is, what if have to specify the starting point of the algorithm. The value h is given as the first element to start.
For example, given the sample array above, if h = 0, we need to start at sample[0][h], therefore the best path would be
110 (Our staring point) + 8 + 10 + 4 = 132
As you can see, the path can only travel downwards or adjacent, therefore if we start at h = 0, there will be values that we cannot reach some values such as 300.
Here is my messy attempt of solving this within the O(N*D) complexity,
# Find max path given h as a starting point
def find_max_path_w_start(mat, h):
res = mat[0][0]
M = len(mat[0])
N = len((mat))
for i in range(1, N):
res = 0
for j in range(M):
# Compute the ajacent sum of the ajacent values from h
if i == 1:
# If h is starting area, then compute the sum, find the max
if j == h:
# All possible
if (h > 0 and h < M - 1):
mat[1][h + 1] += mat[0][h]
mat[1][h] += mat[0][h]
mat[1][h - 1] += mat[0][h]
print(mat)
# Diagona Right not possible
elif (h > 0):
mat[1][h] += mat[0][h]
mat[1][h - 1] += mat[0][h]
# Diagonal left not possible
elif (h < M - 1):
mat[1][h] += mat[0][h]
mat[1][h + 1] += mat[0][h]
# Ignore value that has been filled.
elif j == h + 1 or j == h - 1 :
pass
# Other elements that cannot reach, make it -1
elif j > h + 1 or j < h - 1:
mat[i][j] = -1
else:
# Other elements that cannot reach, make it -1
if j > h + 1 or j < h - 1:
mat[i][j] = -1
else:
# When all paths are possible
if (j > 0 and j < M - 1):
mat[i][j] += max(mat[i - 1][j],
max(mat[i - 1][j - 1],
mat[i - 1][j + 1]))
# When diagonal right is not possible
elif (j > 0):
mat[i][j] += max(mat[i - 1][j],
mat[i - 1][j - 1])
# When diagonal left is not possible
elif (j < M - 1):
mat[i][j] += max(mat[i - 1][j],
mat[i - 1][j + 1])
res = max(mat[i][j], res)
return res
My approach is to only store the reachable values, if example if we start at h = 0, since we are starting at mat[0][h], we can only compute the sum of current and bottom max(mat[1][h] and sum of current and adjacent right mat[1][h + 1]), for other values I mark it as -1 to mark it as unreachable.
This doesn't return the expected sum at the end.
Is my logic incorrect? Are there other values that I need to store to complete this?
You can set all elements of the first row except h to negative infinity, and compute the answer as if there is no starting point restriction.
For example, put this piece of code at the start of your code
for i in range(M):
if i != h:
mat[0][i] = -1e100
Here is a solution which works in a similar way to yours, however it only calculates path sums for at matrix values that could have started at h.
def find_max_path_w_start(mat, h):
M = len(mat[0])
N = len((mat))
for i in range(1, N):
# `h - i` is the left hand side of a triangle with `h` as the top point.
# `max(..., 0)` ensures that is is at least 0 and in the matrix.
min_j = max(h - i, 0)
# similar to above, but the right hand side of the triangle.
max_j = min(h + i, M - 1)
for j in range(min_j, max_j + 1):
# min_k and max_k are the start and end indices of the points in the above
# layer which could potentially lead to a correct solution.
# Generally, you want to iterate from `j - 1` up to `j + 1`,
# however if at the edge of the triangle, do not take points from outside the triangle:
# this leads to the `h - i + 1` and `h + i - 1`.
# The `0` and `M - 1` prevent values outside the matrix being sampled.
min_k = max(j - 1, h - i + 1, 0)
max_k = min(j + 1, h + i - 1, M - 1)
# Find the max of the possible path totals
mat[i][j] += max(mat[i - 1][k] for k in range(min_k, max_k + 1))
# Only sample from items in the bottom row which could be paths from `h`
return max(mat[-1][max(h - N, 0):min(h + N, M - 1) + 1])
sample = [[110, 111, 108, 1],
[9, 8, 7, 2],
[4, 5, 10, 300],
[1, 2, 3, 4]]
print(find_max_path_w_start(sample, 0))
It's easy to build a bottom up solution here. Start thinking the case when there's only one or two rows, and extend it to understand this algorithm easily.
Note: this modifies the original matrix instead of creating a new one. If you need to run the function multiple times on the same matrix, you'll need to create a copy of the matrix to do the same.
def find_max_path_w_start(mat, h):
res = mat[0][0]
M = len(mat[0])
N = len((mat))
# build solution bottom up
for i in range(N-2,-1,-1):
for j in range(M):
possible_values = [mat[i+1][j]]
if j==0:
possible_values.append(mat[i+1][j+1])
elif j==M-1:
possible_values.append(mat[i+1][j-1])
else:
possible_values.append(mat[i+1][j+1])
possible_values.append(mat[i+1][j-1])
mat[i][j] += max(possible_values)
return mat[0][h]
sample = [[110, 111, 108, 1],
[9, 8, 7, 2],
[4, 5, 10, 300],
[1, 2, 3, 4]]
print(find_max_path_w_start(sample, 0)) # prints 132
Let A, B and C be three arrays, each containing N numbers:
A = a[0], a[1], a[2], ..., a[N-1]
B = b[0], b[1], b[2], ..., b[N-1]
C = c[0], c[1], c[3], ..., c[N-1]
I want to select the best k < N elements from A and the best k < N elements from B so that the total sum of their values is maximized. The interesting twist is: If element i is chosen from both A and B (where i in {0, ..., N-1} is the index), then instead of these elements contributing a[i] + b[i], they will contribute c[i] where c[i] >= a[i] + b[i].
At first glance this looked deceptively straightforward to me, but the more I think about the more involved it gets.
I am ultimately looking for an implementation in Python, but at this stage I am just trying to get a sense of what would be an efficient algorithm here.
Example
To clarify, inputs to the algorithm are the 3 N x 1 arrays A, B and C and an integer value for k. The expected output are two k x 1 lists of indices, defining the value-maximizing combination of elements from A and B (and C).
For example, suppose k = 2, N = 4 and let
A = a[0], a[1], a[2], a[3] = 3, 1, 1, 0
B = b[0], b[1], b[2], b[3] = 1, 3, 0, 1
C = c[0], c[1], c[2], c[3] = 4, 4, 3, 2
Even in this simple example, there are many possible combinations. For instance, if elements i = 0, 2 are chosen from A and elements j = 1, 3 are chosen from B, then the total value would be a[0] + a[2] + b[1] + b[3] = 8.
If on the other hand elements i = 0, 1 and j = 0, 1 would be chosen from both A and B, then the special twist applies: Instead of yielding a[0] + a[1] + b[0] + b[1], the total value is given by c[0] + c[1] = 8.
In this example, the combination of elements that maximizes the total value is given by i = 0, 2 from A and elements j = 1, 2 from B. This yields a total value of a[0] + b[1] + c[2] = 9, which can be verified is more than any other combination.
Comparison of answers
Here's a quick comparison of the 3 submitted solutions. First, I checked all of them, and they all give the intended results. As a side comment, none of them requires the elements of C to be weakly larger than the sum of the corresponding elements in A and B, so I dropped this assumption in my performance review.
Here's what I run:
import numpy as np
from utils import tic, toc # simple wrapper to time.perf_counter()
k, N = 10, 1000
A = list(np.random.random_sample([N]))
B = list(np.random.random_sample([N]))
C = list(np.random.random_sample([N]))
tic()
print(optimal_choices(k, A, B, C)) # solution by btilly
toc()
tic()
print(maxPicks(A.copy(), B.copy(), C.copy(), k)) # solution by Eric T-M
toc()
tic()
print(maxSum(A, B, C, k)) # solution by Alain T.
toc()
I tested for various combinations of k and N. It seems that #btilly's algorithm scales well in N as long as k is small. #Alain-T.'s algorithm does the opposite, doing well when k is large relative to N. Across the board, #Eric-T-M's algorithm does best, scaling well in both k and N.
Small problem: k = 10 and N = 500
btilly's algorithm: 0.49s
Eric T-M's algorithm: 0.00s
Alain T.'s algorithm: 0.52s
Small-k, large-N: k = 10 and N = 1000
btilly's algorithm: 0.89s
Eric T-M's algorithm: 0.00s
Alain T.'s algorithm: 1.99s
Large-k, small-N: k = 80 and N = 100
btilly's algorithm: 1.54s
Eric T-M's algorithm: 0.00s
Alain T.'s algorithm: 0.09s
Medium problem: k = 50 and N = 1000
btilly's algorithm: 13.01ss
Eric T-M's algorithm: 0.00s
Alain T.'s algorithm: 8.55s
Large problem 1: k = 10 and N = 1_000_000
Eric T-M's algorithm: 1.03s
Large problem 2: k = 1_000 and N = 100_000
Eric T-M's algorithm: 10.22s
(For the benchmarks, I removed the sorting in Alain T.'s code, to make it comparable.)
Try this. It takes O(N^2) time and it is fairly simple.
def maxPicks(A,B,C,k):
# returns the tuple (list of entries picked in A, list of entries picked in B, total value)
# BASE CASE
if k == 0:
return ([], [], 0)
aMax = max(A)
bMax = max(B)
cMax = max(C)
if (aMax + bMax) > cMax:
aIdx = A.index(aMax)
bIdx = B.index(bMax)
B[aIdx] = C[aIdx] - A[aIdx]
A[aIdx] = -2
C[aIdx] = -1
A[bIdx] = C[bIdx] - B[bIdx]
B[bIdx] = -2
C[bIdx] = -1
nextPicks = maxPicks(A,B,C,k-1)
return (nextPicks[0] + [aIdx], nextPicks[1] + [bIdx], nextPicks[2] + aMax + bMax)
else:
cIdx = C.index(cMax)
A[cIdx] = -1
B[cIdx] = -1
C[cIdx] = -1
nextPicks = maxPicks(A,B,C,k-1)
return (nextPicks[0] + [cIdx], nextPicks[1] + [cIdx], nextPicks[2] + cMax)
Here's how it works:
The base case should be self explanatory. Otherwise we will compare the sum of the maximum of all entries in A and the maximum of all entries in B to the maximum of all entries in C. If this sum is larger than it is safe to pick these entries from A and B, but before making more picks we will need to set the entries we picked as well as their corresponding entries in C to a negative value. As a side note I do assume that all values in A, B and C are originally nonnegative so by setting them negative we forbid our algorithm from picking them again. If this assumption is wrong you might want to set these values to something extremely negative to prohibit double picks. We also see that if we picked A[i] the value of B[i] is now whatever C[i]-A[i] was, because picking B[i] will lose us the value in A[i] and give us the value in C[i] same for the entry A[j] if we pick B[j].
If on the other hand, the greatest entry in C was greater than or equal to aMax+bMax we want to pick it (by picking the corresponding entries in both A and B, because no other picks of entries in A and B or just C alone would be more valuable. At this point we know we don't want to re-pick A[i],B[i], or C[i] again, so we set them all negative.
This can be solved with dynamic programming.
# Helper function to get out of the data structure.
def get_nested_array (data, path):
for x in path:
if data is None or len(data) <= x:
return None
else:
data = data[x]
return data
# Helper function to set data in the data structure.
def set_nested_array (data, path, value):
# Navigate there
for x in path[0:len(path)-1]:
while len(data) <= x:
data.append([])
if data[x] is None:
data[x] = []
data = data[x]
while len(data) <= path[-1]:
data.append(None)
data[path[-1]] = value
# Is this option better than what is there? If so, then add it.
def possibly_add_choice (best_choice, pos, i, j, current_sum, last_i, last_j):
best_prev = get_nested_array(best_choice, [pos, i, j])
if best_prev is None or best_prev[0] < current_sum:
set_nested_array(best_choice, [pos, i, j], (current_sum, last_i, last_j))
# Our program.
def optimal_choices (k, A, B, C):
# best_choice[pos][i][j] = (max_sum, last_a, last_b)
# where:
# We have made all choices in the range 0..pos-1
# We chose i out of A
# We chose j out of B
# and
# max_sum is the best possible sum
# last_a is the last index chosen from a
# last_b is the last index chosen from b
# then we can find the answer working backwards from
# best_choice[len(A)][k][k]
#
best_choice = []
# Enter the empty set answer
set_nested_array(best_choice, [0, 0, 0], (0, None, None))
for pos in range(len(A)):
best_choice.append([])
best_choice_for_pos = best_choice[pos]
for i in range(k+1):
if len(best_choice_for_pos) <= i:
break
best_choice_for_i = best_choice_for_pos[i]
for j in range(k+1):
if len(best_choice_for_i) <= j:
break
last_sum, last_i, last_j = best_choice_for_i[j]
# Try all 4 things we can choose here. Nothing, or A or B or both.
possibly_add_choice(best_choice, pos+1, i, j, last_sum, last_i, last_j)
possibly_add_choice(best_choice, pos+1, i+1, j, last_sum + A[pos], pos, last_j)
possibly_add_choice(best_choice, pos+1, i, j+1, last_sum + B[pos], last_i, pos)
possibly_add_choice(best_choice, pos+1, i+1, j+1, last_sum + C[pos], pos, pos)
# Now we have the answer, it is just a question of decoding it.
if get_nested_array(best_choice, [len(A), k, k]) is None:
return (None, None)
else:
choose_a = []
choose_b = []
best_spot = [len(A), k, k]
max_sum, last_i, last_j = get_nested_array(best_choice, best_spot)
while last_i is not None or last_j is not None:
# Figure out where we last had a choice and what was chosen.
if last_i is None:
last_pos = last_j
i_dec = 0
j_dec = 1
elif last_j is None:
last_pos = last_i
i_dec = 1
j_dec = 0
else:
last_pos = max(last_i, last_j)
i_dec = 0
j_dec = 0
if last_pos == last_i:
i_dec = 1
if last_pos == last_j:
j_dec = 1
# record the choice.
if 1 == i_dec:
choose_a.append(last_pos)
if 1 == j_dec:
choose_b.append(last_pos)
# Go back to that spot
max_sum, last_i, last_j = get_nested_array(best_choice, [last_pos, k-len(choose_a), k-len(choose_b)])
# We walked backwards to generate these lists. So they are currently reversed.
return (list(reversed(choose_a)), list(reversed(choose_b)))
print(optimal_choices(2, [3, 1, 1, 0 ], [1, 3, 0, 1], [4, 4, 3, 2]))
If you expand A and B into a list of index pairs with their respective sums (applying exceptions from C), you can iteratively take the maximum sum and exclude the corresponding pairs at each step. This will select the highest possible totals from the remaining pairs at each iteration:
def maxSum(A,B,C,K):
S = [ ([a+b,C[i]][i==j],i,j) for i,a in enumerate(A)
for j,b in enumerate(B)]
usedA,usedB = set(),set()
for _ in range(K):
_,i,j = max(s for s in S if not(s[1] in usedA or s[2] in usedB))
usedA.add(i)
usedB.add(j)
return sorted(usedA),sorted(usedB)
output:
A = [3, 1, 1, 0]
B = [1, 3, 0, 1]
C = [4, 4, 3, 2]
print(maxSum(A,B,C,2)) # ([0, 2], [1, 2]) total is 9
A = [1,2,3,4]
B = [4,5,6,2]
C = [5,9,9,6]
print(maxSum(A,B,C,2)) # ([1, 3], [1, 2]) total is 19
print(maxSum(A,B,C,3)) # ([1, 2, 3], [0, 1, 2]) total is 26
This is the array.
arr=[1,2,2,1,5,1]
i need to calculate the abs value of index differences between in and all the other elements if the same value, like explained below.
distance metric for a[0] = |0-3|+|0-5|=8
distance metric for a[1] = |1-2|=1
distance metric for a[2] = |2-1|=1
distance metric for a[3] = |3-0|+|3-5|=5
distance metric for a[4] = 0
distance metric for a[5] = |5-0|+|5-3|=7
output is
[8,1,1,5,0,7]
can someone help in coding this in python.
You can use this working example:
arr=[1,2,2,1,5,1]
res = []
for i, n in enumerate(arr):
val = 0
occur = [j for j, x in enumerate(arr) if x == n and j != i]
for o in occur:
val += abs(i-o)
res.append(val)
print(res)
[8, 1, 1, 5, 0, 7]
a bit more efficient version with a complexity of O(n**2)
arr=[1,2,2,1,5,1]
res = []
for i, n in enumerate(arr):
val = 0
for j, x in enumerate(arr):
if x == n and j != i:
val += abs(i-j)
res.append(val)
print(res)
Using list comprehensions and avoiding intermediate variables
arr = [1, 2, 2, 1, 5, 1]
output = []
for index in range(len(arr)):
output.append(sum(abs(index - k) if arr[k] == arr[index] else 0 for k in range(len(arr))))
print(output)
Wrote a simple code for left array rotation, getting the same array without any Rotation done to it as the wrong output.
def leftRotate(arr, d, n):
while (d-1) > 0:
leftRotatebyOne(arr, n)
def leftRotatebyOne(arr, n):
temp = arr[0]
for i in range(n-1):
arr[i] = arr[i + 1]
arr[n - 1] = temp
def PrintArray(arr, size):
for i in range(size):
print("%d" % arr[i], end=" ")
arr = []
l = int(input("Enter the number of elements: "))
for i in range(0, l):
ele = int(input())
arr.append(ele)
d = int(input("Enter the number of rotations: "))
n = len(arr)
leftRotate(arr, d, n)
PrintArray(arr, n)
and here's an example of the output i've got,
Enter the number of elements: 3
1
2
3
Enter the number of rotations: 1
1 2 3
I expected an output of 2 3 1 after one rotation.
I would suggest using array slicing, then adding the slices together, to perform rotation.
def left_rotate(data, num):
return data[num:] + data[:num]
def right_rotate(data, num):
return data[-num:] + data[:-num]
For example
>>> a = [1,2,3,4,5,6,7]
>>> left_rotate(a, 2)
[3, 4, 5, 6, 7, 1, 2]
>>> right_rotate(a, 2)
[6, 7, 1, 2, 3, 4, 5]
Also note that collections.deque has this behavior available already
>>> from collections import deque
>>> d = deque([1,2,3,4,5,6,7])
>>> d.rotate(2)
>>> d
deque([6, 7, 1, 2, 3, 4, 5])
>>> d.rotate(-2)
>>> d
deque([1, 2, 3, 4, 5, 6, 7])
In the function leftRotate,
there is an error in while loop.
Replace
while (d-1) > 0:
leftRotatebyOne(arr, n)
with
while d > 0:
leftRotatebyOne(arr, n)
d -= 1
When d == 1, while (d-1) > 0: will not be executed any time. Also, you never decrement d. The easiest way to solve is by using a for _ in range(d) loop:
def leftRotate(arr, d, n):
for _ in range(d):
leftRotatebyOne(arr, n)
NOTE: Python has way better ways to do rotations than this. This code seems to be C more than Python. Passing the array length makes no sense in Python for example. And the rotation can be done all in one assignation.
def leftRotate(arr, d):
d %= len(arr)
for _ in range(d):
arr[-1], arr[:-1] = arr[0], arr[1:]
Cory Kramer's answer is even more pythonic. But it has a bug and a difference with your question's methods. The bug is that it doesn't work when the number of rotations requested are higher than the length of the list. The difference is that they are returning a new list instead of modifying it. These two issues could be addresed like this:
def left_rotate(data, num):
num %= len(data)
data[:] = data[num:] + data[:num]
def right_rotate(data, num):
num %= len(data)
data[:] = data[-num:] + data[:-num]
The question is classical 0-1 knapsack problem:
Given n items with size A[i], an integer m denotes the size of a backpack. How full you can fill this backpack?
Example
If we have 4 items with size [2, 3, 5, 7], the backpack size is 11, we can select 2, 3 and 5, so that the max size we can fill this backpack is 10. If the backpack size is 12. we can select [2, 3, 7] so that we can fulfill the backpack.
I wrote two solution for it, and the first recursion one works, but DP one doesn't.
class Solution:
# #param m: An integer m denotes the size of a backpack
# #param A: Given n items with size A[i]
# #return: The maximum size
def backPack(self, m, A):
if len(A) <= 0 or m <= 0:
return 0
if A[0] > m:
return self.backPack(m, A[1:])
put = A[0] + self.backPack(m - A[0], A[1:])
not_put = self.backPack(m, A[1:])
return max(put, not_put)
def YetAnotherBackPack(self, m, A):
mapping = [(m + 1) * [0]] * (len(A) + 1)
for i in range(len(A) + 1):
for j in range(m + 1):
if i == 0 or j == 0:
mapping[i][j] = 0
elif A[i - 1] > j:
mapping[i][j] = mapping[i - 1][j]
else:
put = mapping[i - 1][j - A[i - 1]] + A[i - 1]
mapping[i][j] = max(mapping[i - 1][j], put)
return mapping[len(A)][m]
print Solution().backPack(10, [3, 4, 8, 5]) # output: 9
print Solution().YetAnotherBackPack(10, [3, 4, 8, 5]) # output: 10 WRONG!
Can anyone help point that what's wrong with my DP solution?
This line is the problem:
mapping = [(m + 1) * [0]] * (len(A) + 1)
You're creating a list of lists, but you're not creating a unique inner list for each row - all of the rows are pointing to the same list (the one created by [(m + 1) * [0]].
To fix it, change that line to something like this:
mapping = [[0 for i in range(m+1)] for j in range(len(A) + 1)]
For a more detailed description of this issue: Nested List Indices