Dynamic Programming - minimize the sum of the array - python

My problem is: given an array of negative and positive integers. You are given value j to jump and r to rest. After each jump, you need to rest for r steps. Moreover, you are allowed to move 1 more step forward even when you have the ability to jump. The problem is to minimize the sum of the array.
Ex.1 r = 2, j = 2, [5, 3, -4, -2, 1, 2, 3] => -4 + -2 + 3 = -3 (Jump 5, 3, Rest -4,-2, Jump 1,2, Rest 3)
Ex.2 r = 2, j = 3, [90, 91, 92, -2, 3, 55, 3] => -2 + 3 + 55 + 3 = 59 (Jump 90,91,92 Rest -2,3,55,3)
My Idea: I decided to use DP to solve this. This is my pseudocode.
def minimalSum (MIN, array, jump, rest, steps_left_to_jump, i):
if MIN[i] is not empty:
return MIN[i]
if i == len(array) - 1:
MIN[i] = array[i]
else:
if steps_left_to_jump == 0:
if i == 0:
MIN[i] = minimalSum(MIN, array, jump, rest, rest - 1, jump)
else:
if i + jump + 1 < len(array):
MIN[i] = array[i] + minimalSum(MIN, array, jump, rest, rest - 1, i + jump + 1)
o1 = array[i] + minimalSum(MIN, array, jump, rest, 0, i + 1)
if MIN[i] is not None:
if o1 < MIN[i]:
MIN[i] = o1
else:
MIN[i] = o1
else:
MIN[i] = array[i] + minimalSum(MIN, array, jump, rest, steps_left_to_jump - 1, i + 1)
return MIN[i]
MIN is array used to store best sums.
The problem that it does not work for all inputs, can you help me spot where I am going wrong. Consider the example
r = 2, j = 2 , [2 ,-2 ,-3,1 ,3 ,4]. The answer should be 1 (Visit 2, -2, Jump -3, Rest 4) 2-2-3+4 = 1, but my program outputs 5

Your problem seems to be in this line:
if i == 0:
MIN[i] = minimalSum(MIN, array, jump, rest, rest - 1, jump)
This prevents you from ever choosing Visit whenever i is 0, since you ALWAYS jump in your first step. I don't know about your full code, but this part should be:
if i == 0:
MIN[i] = min(minimalSum(MIN, array, jump, rest, rest - 1, jump) , # case where you jump at 0
array[0] + minimalSum(MIN, array, jump, rest, 0, 1) # case where you visit index 0
)
Also, your code give you an out-of-bounds error if jump>len(MIN)-1. If this condition is true, you know you should ALWAYS visit.
Given all of this, I'm going to write the recursive formula, you can then memoize it:
def opt_sum(array, r, j):
if j > len(array)-1:
return sum(array) # cannot jump, return sum of full array
else:
visit_val = array[0] + opt_sum(array[1:], r, j) # best sum if you choose to visit index 0
jump_val = (
sum(array[j:j+r]) # total mandatory resting visits
+ opt_sum(array[j+r:], r, j) # the optimal sum of the subarray
) # starting after the rest
return min(visit_val, jump_val) # return best of both policies

Related

Function to sum the first and last item in an array until there's only two items left in the array

I've recently failed a coding challenge and I'd like to know the solution to the problem.
Here's what was asked of me (from memory as I do not have access to the question anymore):
Create a function that takes an array of integers and sums the first and last item, then the second and second to last, and so on. You must do this until there's only two items left in the returning array.
Example:
Input: [1, 4, 2, 3, 6, 10]
Returns: [16, 10]
Because:
[(1 + 10), (4 + 6), (2 + 3)] =
[11, 10, 5] =>
[(11 + 5), 10] =
[16, 10]
Example 2:
Input: [-1, 3, 2, -2, 11, 7, -9]
Returns: [-12, 23]
Because:
[(-1 + (-9)), (3 + 7), (2 + 11), -2] =
[-10, 10, 13, -2] =>
[(-10 + (-2)), (10 + 13)] =
[-12, 23]
Constraints:
-101 < arr[i] < 101
You may assume that each arr[i] is unique
Here's my attempt at solving this problem:
def sumFirstAndLast (array):
if len(array) == 2:
return array
else:
left = 0
right = len(array) - 1
result = []
while left < right:
result.append(array[left] + array[right])
left += 1
right -= 1
array = result
result = sumFirstAndLast(array)
return result
But it's throwing an error:
Traceback (most recent call last):
File "<string>", line 22, in <module>
File "<string>", line 11, in sumFirstAndLast
IndexError: list index out of range
>
Can someone provide me with a solution to this problem? Why am I getting this error? Is my logic to approaching this problem incorrect?
array = result and result = sumFirstAndLast(array) should be put outside the while loop
left could be equal to right if there are odd elements. After while left < right we need an additional condition check if left == right.
The code can be as follows:
def sumFirstAndLast (array):
if len(array) == 2:
return array
else:
left = 0
right = len(array) - 1
result = []
while left < right:
result.append(array[left] + array[right])
left += 1
right -= 1
if left == right:
result.append(array[left])
return sumFirstAndLast(result)
Some issues:
The indentation of two statements is wrong. The recursive call should not be made within the loop, but once the loop has been completed.
When the size of the array is odd, then the middle element is currently ignored and drops out. It should still be added to the result.
Not a huge problem if it is guaranteed, but you should better guard your function for the case the array has fewer than 2 values.
So without changing anything else to your code, it would become:
def sumFirstAndLast (array):
if len(array) <= 2:
return array
else:
left = 0
right = len(array) - 1
result = []
while left < right:
result.append(array[left] + array[right])
left += 1
right -= 1
if left == right: # There is a middle element. It needs to be retained
result.append(array[left])
array = result
result = sumFirstAndLast(array) # recursion outside of the loop
return result
In a more compact, iterative version, it could be:
def sumFirstAndLast (array):
while len(array) > 2:
array = [
array[i] + array[-i-1] for i in range(len(array) // 2)
] + ([array[len(array) // 2]] if len(array) % 2 else [])
return array
I think recursion makes it harder to reason about what's going on (and thus, it's harder to reason about what's going wrong).
I would instead solve this iteratively, repeatedly performing the operation until you have a list with only two elements. The code ends up being pretty similar to the recursive version, but in my opinion it's much more readable (and thus much easier to debug):
def sumFirstAndLast (array):
if len(array) == 2:
return array
result = array.copy()
while len(result) > 2:
left = 0
right = len(result) - 1
new_result = []
while left < right:
new_result.append(result[left] + result[right])
left += 1
right -= 1
if left == right:
new_result.append(result[left])
result = new_result
return result
I'm gonna try to give you some tips as well.
Basically when you are solving a problem, you should break it into smaller problems. Start for the input. As you are working with operations 2 to 2, you should think, well what will happen if the input is minor than 2, that is the minimun required number for you to operate. Second, what happens if my input is odd or even? is it the same? In this case not. Lastly, try to approach a single loop and see the output before looping the whole thing and get a messy output.
With that in mind, you will solve the problem if the provided input doesn't have a minimun required size for operation:
if len(array) < 2:
return "Array must be of size bigger than 2"
Solve a single loop for odd cases:
for i in range(int(len(array) / 2) + len(array) % 2):
if i < len(array) - i - 1:
new_array.append(array[i] + array[len(array) - i - 1])
Solve a single loop for even cases:
for i in range(int(len(array) / 2) + len(array) % 2):
if i < len(array) - i - 1:
new_array.append(array[i] + array[len(array) - i - 1])
if len(array) % 2:
new_array.append(array[int(len(array) / 2)])
Now you have a single case loop, do the complete loop:
while len(array) > 2:
for i in range(int(len(array) / 2) + len(array) % 2):
if i < len(array) - i - 1:
new_array.append(array[i] + array[len(array) - i - 1])
if len(array) % 2:
new_array.append(array[int(len(array) / 2)])
array = new_array
new_array = []
Join all of that together and you have your Solution:
def solve(array):
if len(array) < 2:
return "Array must be of size bigger than 2"
new_array = []
while len(array) > 2:
for i in range(int(len(array) / 2) + len(array) % 2):
if i < len(array) - i - 1:
new_array.append(array[i] + array[len(array) - i - 1])
if len(array) % 2:
new_array.append(array[int(len(array) / 2)])
array = new_array
new_array = []
return array
At the end you can search for a compact solution, because now you at least solved the question in the interview, and got the logic.
def solve(array):
if len(array) < 2:
return "Array must be of size bigger than 2"
while len(array) > 2:
remainder = (array[int(len(array) / 2)]) if len(array) % 2 else False
array = [(array[i] + array[len(array) - i - 1]) for i in range(int(len(array) / 2))]
array.append(remainder) if remainder is not False else None
return array

how can solve this problem with dynamic programming?

Instructions : rat can move just up or right
input:
The first line contains the number of table size n and the number of cheese m.
From the next line, the position x, y of the cheese is given
Output :
The maximum number of cheese to eat
Exemple1:
input : 1 1 1
output : 1 1
Example2:
input :
3 2
1 2
3 1
output : 1
Example 3:
input :
5 5
2 3
3 2
4 3
4 5
5 2
output: 3
how can I solve with python?
I tried
def maxAverageOfPath(table, N):
dp = [[0 for i in range(N)] for j in range(N)]
dp[0][0] = table[0][0]
# Initialize first column of total table(dp) array
for i in range(0, N):
dp[i][0] = 0
for j in range(0, N):
dp[0][j] = 0
for i in range(0, N):
for j in range(0, N):
print(i, j)
if i == N-1 and j == N-1:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
continue
if i == N-1 :
dp[i][j] = table[i][j + 1]
continue
if j == N-1 :
dp[i][j] = table[i + 1][j]
continue
dp[i][j] = max(table[i + 1][j], table[i][j + 1])
return dp
but failed...
For dynamic programming you want an edge condition(s) and a way to score where you are right now. After that it's more-or-less smart brute force. The smart part comes from memoizing so you don't repeat work.
Here's a basic recursive approach for python that does the following:
Organize table of cheese as a frozen set of tuples. This can be hashed for memoization and you can determine of a location is in the set in constant time.
Creates an edge condition for the end (when both coordinates are N) and an edge condition for when you walk off the map -- that just returns 0.
Uses lru_cache to memoize. You can implement this yourself easily.
from functools import lru_cache
def hasCheese(table, location):
''' Helper function just to improve readability '''
return 1 if location in table else 0
#lru_cache()
def maxC(table, N, location = (0, 0)):
# edge conditions final square and off the grid:
if location[0] == N and location[1] == N:
return hasCheese(table, location)
if any(l > N for l in location):
return 0
# recursion
score_here = hasCheese(table, location)
return max(score_here + maxC(table, N, (location[0] + 1, location[1])),
score_here + maxC(table, N, (location[0], location[1] + 1))
)
t = frozenset([(2, 3), (3, 2), (4, 3), (4, 5), (5, 2)])
N = 5
print(maxC(t, N))
# prints 3
If you want to do this in a top-down manner using a matrix, you need to be very careful that you always have the previous index set. It's easier to make mistakes doing it this way because you need to get the indexes and order just right. When you set it up as two nested increasing loops, that means the next value is always the current cell plus the max of the two cells one unit less — you should always be looking backward in the matrix. It's not clear what you are trying to do when you are looking forward with this:
dp[i][j] = table[i][j + 1]
because j+1 has not been determined yet.
Since the cheese coordinates are 1 indexed, an easy way forward is to make your matrix zero indexed and N+1 in size. Then when you start your for loops at 1 you can always look and at lower index without undershooting the matrix and avoid a lot of the if/else logic. For example:
def hasCheese(table, location):
''' Helper function just to improve readability '''
return 1 if location in table else 0
def maxAverageOfPath(table, N):
# matrix is sized one unit bigger
dp = [[0 for i in range(N+1)] for j in range(N+1)]
# iterate 1-5 inclusive
for i in range(1, N+1):
for j in range(1, N+1):
# because the zeroth row and column are already zero, this works without undershooting the table
dp[i][j] = hasCheese(table, (i, j)) + max(dp[i][j-1], dp[i-1][j])
# solution is in the corner
return dp[N][N]
t = {(2, 3), (3, 2), (4, 3), (4, 5), (5, 2)}
N = 5
print(maxAverageOfPath(t, N)) #3
When you'r done your matrix will look like:
[0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0]
[0, 0, 0, 1, 1, 1]
[0, 0, 1, 1, 1, 1]
[0, 0, 1, 2, 2, 3]
[0, 0, 2, 2, 2, 3]
Your starting point is at (1, 1) starting in the top-right and your answer is the bottom left corner.
At each point you have two options to move further :
array [row] [col+1]
array [row+1] [col]
As we have to find out a path which involves max cheese.
It can be achived by recurring through the array like below to solve the same:
Solution =>
array [i] [j] + Max(Recur(array [i] [j+1]), Recur(array [i+1] [j]));

Programming Maze Solution recursively

This function is intended to recursively navigate a maze and find the length of the shortest path. The path itself is not necessary, only the length. The maze is represented by a 2d list with values such as
0 1 0 0 0
0 0 0 1 0
0 0 0 1 0
The user starts at (0,0) and must end up at the end of the maze as defined (in my case it is the bottom right cell). 1's represent walls.
def maze(x,y,array,length):
m = len(array)
n = len(array[0])
if x < 0 or y < 0 or x == m or y == n or array[x][y] == 1:
return float("inf")
elif x == m - 1 and y == n - 1:
return length
else:
array[x][y] = 1
up = maze(x - 1,y,array,length + 1)
right = maze(x,y + 1,array,length + 1)
down = maze(x + 1,y,array,length + 1)
left = maze(x,y - 1,array,length + 1)
return min(up,down,left,right)
array = [[0,1,0,0,0],[0,0,0,1,0],[0,0,0,1,0]]
minLength = maze(0,0,array,1)
print(minLength)
I designed it so that it recursively finds all possible paths from each direction (up, down, left and right), and returns the lowest value from all these paths with each step of the way. It returns inf for any path that is not valid.
For this specific array, it returns 11, which is false, it should be 9. I do not believe it is merely a mathematical error, as I tried printing each step of the way and it is not recognizing certain paths (it returns inf for paths that most definitely have options).
I can't seem to find where my code is going wrong, it seems like it should properly return the value, but in practice it does not.
array is a reference to the original array, not a local copy. See any of the on-line tutorials on how Python passes function arguments, or how it handles lists. You can see the effect by printing array in your main program after the call to maze:
Final Maze [
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 0]
]
Fixing this is relatively easy: copy the nested list and use that copy locally.
from copy import deepcopy
def maze(x,y,array,length):
m = len(array)
n = len(array[0])
if x < 0 or y < 0 or x == m or y == n or array[x][y] == 1:
return float("inf")
elif x == m - 1 and y == n - 1:
return length
else:
new_maze = deepcopy(array)
new_maze[x][y] = 1
up = maze(x - 1,y,new_maze,length + 1)
right = maze(x,y + 1,new_maze,length + 1)
down = maze(x + 1,y,new_maze,length + 1)
left = maze(x,y - 1,new_maze,length + 1)
return min(up,down,left,right)
array = [[0,1,0,0,0],[0,0,0,1,0],[0,0,0,1,0]]
minLength = maze(0,0,array,1)
print("Final Maze", array)
print(minLength)
The output from this is (edited for readability again)
Final Maze [
[0, 1, 0, 0, 0],
[0, 0, 0, 1, 0],
[0, 0, 0, 1, 0]
]
9

Dynamic change-making algorithm that returns actual list of coins used

I'm trying to adjust the code from the wikipedia:
https://en.wikipedia.org/wiki/Change-making_problem#Implementation
To also output the list of coins used, not only the number of coins used. That is, for instance:
change_making([6, 8, 12], 52) outputs 5 which is correct (12+12+12+8+8 = 52).
The problem is that I want to get output in this format [12, 12, 12, 8, 8] instead of just 5 and I have no idea how to do that.
The code in question:
def _get_change_making_matrix(set_of_coins, r):
m = [[0 for _ in range(r + 1)] for _ in range(len(set_of_coins) + 1)]
for i in range(r + 1):
m[0][i] = i
return m
def change_making(coins, n):
"""This function assumes that all coins are available infinitely.
n is the number that we need to obtain with the fewest number of coins.
coins is a list or tuple with the available denominations."""
m = _get_change_making_matrix(coins, n)
for c in range(1, len(coins) + 1):
for r in range(1, n + 1):
# Just use the coin coins[c - 1].
if coins[c - 1] == r:
m[c][r] = 1
# coins[c - 1] cannot be included.
# We use the previous solution for making r,
# excluding coins[c - 1].
elif coins[c - 1] > r:
m[c][r] = m[c - 1][r]
# We can use coins[c - 1].
# We need to decide which one of the following solutions is the best:
# 1. Using the previous solution for making r (without using coins[c - 1]).
# 2. Using the previous solution for making r - coins[c - 1] (without using coins[c - 1]) plus this 1 extra coin.
else:
m[c][r] = min(m[c - 1][r], 1 + m[c][r - coins[c - 1]])
return m[-1][-1]
Any help/suggestion would be greatly appreciated.
------------- EDIT -------------
The solution (comments removed):
def _change_making(coins, n):
m = [[0 for _ in range(n + 1)] for _ in range(len(coins) + 1)]
for i in range(n + 1):
m[0][i] = i
for c in range(1, len(coins) + 1):
for r in range(1, n + 1):
if coins[c - 1] == r:
m[c][r] = 1
elif coins[c - 1] > r:
m[c][r] = m[c - 1][r]
else:
m[c][r] = min(m[c - 1][r], 1 + m[c][r - coins[c - 1]])
i = len(coins)
j = n
ret = {k: 0 for k in coins}
while j != 0:
if m[i][j - coins[i - 1]] == m[i][j] - 1:
ret[coins[i - 1]] += 1
j = j - coins[i - 1]
else:
i = i - 1
return ret
To find the closest * solution:
def change_making(coins, n):
try:
return _generate_packing(coins, n)
except:
return generate_packing(coins, n + 1)
For instance change_making([2, 5], 8)
{2: 2, 5: 1}
Because 9 is the closest possible solution.
By closest I mean a solution that is possible to satisfy but above the original request. For instance if we need to return £8 in change and we do not have the exact change, well, we will return £9 because we do have change for that.
Here are steps how you can do it -
1)Start with i=len(coins) and j=n ie end of your array(or list) m
2)Now we know a coin of value coins(i-1) is chosen if m[i][j] uses exactly one more coin than m[i][j-coins[i-1]].
3)If this doesnt happen we check the other coins(coins at lower index in list) for same condition.
Example-
At start we have value 52 and we have solved that it needs 5 coins using your your function.
We use first coin of 12 only if for value 40(ie 52 -12) we need 4 coins and similarly for for 2nd and 3rd 12 valued coin.
But we cant use fourth 12 coin as value 4(ie 16-12) cant be achieved using 1 coin.
Here is code snippet to do same(you can it use at end of your function instead of return statement) -
i=len(coins)
j = n
while(j!=0):
if m[i][j-coins[i-1]] == m[i][j]-1:
print(coins[i-1])
j=j-coins[i-1]
else:
i=i-1

Fill in an array using loop with multiple variables (new to Python, old to C++ (back in the day))

Basically what I want to do is create something like this in python (this is basic idea and not actual code):
n = 3
i = n + 1
a = [1, 3, 3, 1]
b = [1, 2, 1]
while n > 1:
Check if n is even
- if n is even, then for all i in range(0,n), insert values into an array using the formula below
- b[n-i] = a[n-i-1] + a[n-i], this value will replace the previously given value of b[] above the code.
- Print out the array
- After each area is filled, n+=1, i=n+1 are applied, then the loop continues
Check if n is odd
- same process except formula is
- a[n-i] = b[n-i-1] + a[n-i], this value will replace the previously given value of a[] above the code.
- Print out the array
- After each area is filled, n+=1, i=n+1 are applied, then the loop continues
This process will loop and print each and continue on, the arrays will essentially look like this:
b = [1, 4, 6, 4, 1], a = [1 5, 10, 10, 5, 1], b = [1, 6, 15, 20, 20, 15, 6, 1], etc.
Here is the code that I currently have, however I'm getting an 'out of range' error.
n = 3
i = n + 1
b = [1, 2, 1]
a = [1, 3, 3, 1]
while n > 1:
if n%2==0:
print("even")
for i in range(0,n):
b[n-i].append(a[n-i-1]+a[n-i])
else:
print("odd")
for i in range(0,n):
print("yay")
a[n-i].append(b[n-i-1]+b[n-i])
if n%2==0:
print(b)
else:
print(a)
n +=1
i = n + 1
print("loop")
The random prints throughout the code are to test and see if it is even making it into the process. There were from a previous code and I just haven't removed them yet.
Hopefully you can help me, I can't find anything online about a loop that constantly increases the size of an array and fills it at the same time.
Sorry struggling with the code that's in the sample. From your description I can see that you want to generate Pascal's triangle. Here's a short snippet that will do this.
a = [1, 1]
for _ in range(10):
a = [1] + [x+y for (x,y) in zip(a[:-1], a[1:])] + [1]
print a
a[:-1] refers to the whole array except the last element and a[1:] refers to whole array except first element. zip combines first elements from each array into a tuple and so on. All that remains is to add them and pad the row with ones one the outside. _ is used to tell Python, I don't care about this variable - useful if you want to be explicit that you are not using the range value for anything except flow control.
Maria's answer is perfect, I think. If you want to start with your code, you can rewrite your code as below to get similar result. FYI.
n = 3
b = [1, 2, 1]
while 1 < n < 10:
if n % 2 == 0:
print("even")
b = [0] * (n + 1)
for i in range(0, n + 1):
if i == 0:
b[i] = a[0]
elif i == n:
b[i] = a[i - 1]
else:
b[n - i] = a[i - 1] + a[i]
else:
print("odd")
a = [0] * (n + 1)
for i in range(0, n + 1):
if i == 0:
a[i] = b[0]
elif i == n:
a[i] = b[i - 1]
else:
a[i] = b[i - 1] + b[i]
if n % 2 == 0:
print(b)
else:
print(a)
n += 1
print("loop")

Categories