What's wrong with my DP solution for 0-1 Knapsack? - python

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

Related

Write an algorithm for the sequence

Calculate the n member of the sequence given by the formulas
a[2 * n] = a[n] + 1
a[2 * n + 2] = a[2 * n + 1] - a[n]
a[0] = a[1] = 1
n > 0
I've tried a lot of variants, but I can't find correct one.
n = int(input())
a = [0 for i in range(n + 3)]
a[0] = a[1] = 1
i = 1
while i * 2 + 2 < n + 3:
a[2 * i] = a[i] + 1;
a[2 * i + 1] = a[2 * i + 2] + a[i]
a[2 * i + 2] = a[2 * i + 1] - a[i]
i += 1
print(a[n])
We should first compute the expected output for the first few numbers to let us have an idea what the sequence is like first,
a[0] = a[1] = 1
Substitute n = 1 in the first recurrence relation gives
a[2] = a[1] + 1 = 2
Substitute n = 1 in the second recurrence relation gives
a[4] = a[3] - a[1]
But a[4] = a[2] + 1 = 3 according to the first recurrence relation, so 3 = a[3] - 1, which gives a[3] = 4
We have a = {1, 1, 2, 4, 3, ... }
Your program gives a = {1, 1, 2, 1, 3, ...}
What went wrong in your program?
We notice that when i = 1, the line a[2 * i + 1] = a[2 * i + 2] + a[i] evaluates to a[3] = a[4] + a[1]. However, at that time, a[4] is not evaluated yet, causing an incorrect output.
The issue, therefore, lies in how you order your statements in the while loop. Make sure that statements in your loop only make use of values that will not be changed later.
How should we do that?
if we manipulate the second recurrence relation as follows:
a[2 * i + 2] = a[2 * i + 1] - a[i]
a[2 * i + 1] = a[2 * (i + 1)] + a[i]
Using the first recurrence relation, we have
a[2 * i + 1] = a[i + 1] + 1 + a[i]
which should resolve the issue since 2 * n + 1 > n + 1 for all positive n.
After modifying the second statement, you check that every element in a is computed and you should be done.
Note
One more thing to note is that the third statement is redundant since the first statement covers all even elements in a already.
In fact, a more efficient approach, in particular a logarithmic solution exist2 if you only have to calculated the nth member of the sequence.
I found decision
n = int(input())
k = n if n % 2 == 0 else n + 1
a = [None for i in range(k + 1)]
a[0] = a[1] = 1
def fill_list(a):
while None in a:
i = 1
while i * 2 <= k:
if a[i] != None:
a[2 * i] = a[i] + 1
i += 1
i = 1
while i * 2 + 2 <= k:
if a[i * 2 + 2] != None and a[i] != None:
a[i * 2 + 1] = a[i * 2 + 2] + a[i]
i += 1
fill_list(a)
print(a[n])
Your second formula gives a[2n+2] = a[2n+1] - a[n]. That can be rewritten: a[2n+1] = a[2n+2] + a[n] which is a[n+1] + a[n] + 1 from the first formula.
We can use this to write a simple dynamic programming algorithm that runs in linear time:
def A(n):
a = [1] * (n+1)
for i in range(2, n+1):
if i%2 == 0:
a[i] = a[i//2] + 1
else:
a[i] = a[i//2] + a[i//2+1] + 1
return a[n]
However, we can note that we can solve this in logarithmic time, by noting that we can compute both a[n] and a[n+1] from a[n//2] and a[n//2+1].
If n is even, then a[n]=a[n//2]+1 and a[n+1]=a[n//2]+a[n//2+1]+1.
And if n is odd, then a[n]=a[n//2]+a[n//2+1]+1 and a[n+1]=a[n//2+1]+1.
These are just applications of the formulas we have already.
This gives us this solution:
def A2(n):
if n == 0:
return 1, 1
if n == 1:
return 1, 2
a, b = A2(n//2)
if n % 2 == 0:
return a+1, a+b+1
else:
return a+b+1, b+1
Note that this returns 2 values, but for all n, A(n) == A2(n)[0].

Python updating list when I don't want it to

first time on stack overflow. I am very new to python and programming in general.
This particular function is supposed to take 3 matrices as inputs: two N x 1 matrices and an N x N matrix. Using one of the matrices, it is supposed to sort all values of 1 to the top, and all values of 0 to the bottom. This works fine. The N x N matrix is supposed to swap columns that correspond to the rows that were swapped in the N x 1 matrix: i.e. if rows 3 and 4 are swapped in the N x 1 matrix, then columns 3 and 4 need to be swapped in the N x N matrix.
The issue is that, in the inner for loop, it seems to be updating the values of the variables as soon as the k_bar matrix (N x N) is changed, which causes all columns to have the same value in every row by the end of the iterations. I have no idea how to stop this, or why it is doing it.
If I print out the values immediately before and after the lines
k_bar[0][k, (i + 1)] = temp_left[0][k] and k_bar[0][k, i] = temp_right[0][k] as it loops through the inner for loop, it prints array([1, 4, 7]) array([2, 5, 8]) and then on the next iteration it prints array([1, 4, 7]) array([1, 5, 8]) and keeps going until both read array([1, 4, 7]).
Any help is appreciated.
Here is my code:
import numpy as np
def reorder(r, k_bar, f):
import numpy as np
num_react = 0
for i in range(len(r)):
if r[i][0] == 0:
pass
else:
num_react += 1
consecutive_count = 0
while(consecutive_count != num_react):
for i in range(len(r) - 1):
if r[i][0] == 0:
temp_above = r[i][0]
temp_below = r[i + 1][0]
r[i][0] = temp_below
r[i + 1][0] = temp_above
temp_above2 = f[i][0]
temp_below2 = f[i + 1][0]
f[i][0] = temp_below2
f[i + 1][0] = temp_above2
temp_left = []
temp_left.append(k_bar[0][:, i])
temp_right = []
temp_right.append(k_bar[0][:, (i + 1)])
for k in range(len(r)):
k_bar[0][k, (i + 1)] = temp_left[0][k]
k_bar[0][k, i] = temp_right[0][k]
else:
consecutive_count += 1
if consecutive_count == len(r) and consecutive_count != num_react:
consecutive_count = 0
print(r)
print(k_bar)
print(f)
ke = []
fe = []
re = []
ke.append(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype = int))
fe.append(np.array([0], dtype = int))
fe.append(np.array([0], dtype = int))
fe.append(np.array([1], dtype = int))
re.append(np.array([0], dtype = int))
re.append(np.array([0], dtype = int))
re.append(np.array([1], dtype = int))
print(ke, fe, re)
reorder(re, ke, fe)
I figured it out. The temp_left and temp_right were being considered as the same list as k_bar. Used temp_left.append(k_bar[0][:, i].copy()) and it worked fine.

Breeding new child list from two same length lists, in order, without duplicates and splitting at random index

In this problem I am trying to create a new list of length n from two lists of length n each. I randomly select a subset of the first parent list (using start and end variables) and add them to the new list in the same positions in which they appeared in their corresponding list. Then I populate the remainder of the new list with elements from the second parent list in the order in which they appear, without duplicating any element that was selected from the first parent. The image explains it.
Here is my python code: a plane here is weights for a Perceptron model.
def breed(plane1, plane2):
num_list = list(range(0, len(plane1)))
random.shuffle(num_list)
n1 = num_list[0]
n2 = num_list[1]
start = min(n1, n2)
end = max(n1, n2)
child = [None] * len(plane1)
for i in range(start, end):
child[i] = plane1[i]
idx = (end) % len(plane2)
for i in range(len(plane2)):
pos = (end + i) % len(plane2)
if plane2[pos] not in child:
child[idx] = plane2[pos]
idx = (idx + 1) % len(plane2)
return child
Can anyone recommend a different way that is efficient and concise?
Also, the end of random range is not included in the selection:
one = [1,2,3,4,5,6,7,8,9]
two = [9,8,7,6,5,4,3,2,1]
child = breed(one, two)
print(child)
start: 0
end: 7
Output:
[1, 2, 3, 4, 5, 6, 7, 9, 8]
Here's a solution. It could probably be better, not using a while loop would be more elegant. I don't know if it covers all edge cases. I've split out the logic from the randomly generated numbers to make it easier to test.
import random
def breed(plane1, plane2):
assert len(plane1) == len(plane2)
istart = random.randint(0, len(plane1) - 2)
iend = random.randint(istart + 1, len(plane1) - 1)
print(f"random indices: {istart} to {iend}")
return generate_breed(plane1, plane2, istart, iend)
def generate_breed(plane1, plane2, istart, iend):
child = [-1 for _ in plane1]
child[istart : iend + 1] = plane1[istart : iend + 1]
i = j = 0
while True:
if j == istart:
j = iend + 1
if j >= len(child):
break
if plane2[i] not in child:
child[j] = plane2[i]
j += 1
i += 1
return child
if __name__ == "__main__":
p1, p2 = [1, 2, 3, 4, 5, 6], [7, 3, 2, 1, 2, 6]
start, end = 2, 4
assert generate_breed(p1, p2, start, end) == [7, 2, 3, 4, 5, 1]
assert generate_breed([1, 2, 3], [4, 2, 1], 0, 2) == [1, 2, 3]
# call like this, but answer is unpredictable due to randint call
print(breed([1, 2, 3], [4, 2, 1]))
Edit
Original answer was super wrong, after iterating over this for a while, this works, assuming lists don't have duplicates in themselves.
Magic Numbers
With generating the start and end, the magic -2 and magic +1 can be tuned if you want to set a min length. I set the min length here to 1.
def breed(plane1, plane2):
i = randint(0, len(plane1) - 2)
j = randint(i + 1, len(plane1))
plane2 = [x for x in plane2 if x not in plane1[i:j]]
child = plane2[0:i] + plane1[i:j] + plane2[i:len(plane1) - (j - i)]
return child

Maximum sum path in the matrix with a given starting point

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

Merge Sort in Python Bug

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.

Categories

Resources