Python Knapsack Problem with fixed number of elements - python

So far I have the following code which I believe selects up to a MAX of 4 (or n) elements in the knapsack (hence the 3rd dimension). However, I want to ensure that the code ALWAYS selects 4 (or n) elements. Can someone please advise as I can't find anything about this anywhere...
def knapsack2(n, weight, count, values, weights):
dp = [[[0] * (weight + 1) for _ in range(n + 1)] for _ in range(count + 1)]
for z in range(1, count + 1):
for y in range(1, n + 1):
for x in range(weight + 1):
if weights[y - 1] <= x:
dp[z][y][x] = max(dp[z][y - 1][x],
dp[z - 1][y - 1][x - weights[y - 1]] + values[y - 1])
else:
dp[z][y][x] = dp[z][y - 1][x]
return dp[-1][-1][-1]
w = 10
k = 4
values = [1, 2, 3, 2, 2]
weights = [4, 5, 1, 1, 1]
n = len(values)
# find elements in
elements=[]
dp=m
while (n> 0):
if dp[k][n][w] - dp[k][n-1][w - weights[n-1]] == values[n-1]:
#the element 'n' is in the knapsack
elements.append(n)
n = n-1 #//only in 0-1 knapsack
w -= weights[n]
else:
n = n-1

Related

Key Error: 0 in Python with edit distance

Could someone please let me know why I am getting a "Key Error: 0" message with reference to line 23 for the following code? I am trying to implement an edit distance function, returning the cost and the last operation. Thanks!
from enum import Enum
class Operation(Enum):
"""Operations"""
DELETED = 1
INSERTED = 2
SUBSTITUTED = 3
def __str__(self):
return str(self.name.lower())
def distances(a, b):
"""Calculate edit distance from a to b"""
# edit distance
x = len(a) + 1
y = len(b) + 1
cost = {}
for i in range(0, x):
cost[i][0] = i
for j in range(0, y):
cost[0][j] = j
for i in range(1, x):
for j in range(1, y):
if a[i] == b[j]:
sub_cost = 0
else:
sub_cost = 1
cost[i][j] = min(cost[i - 1][j] + 1, cost[i][j - 1] + 1, cost[i - 1][j - 1] + sub_cost)
# final operation
if cost[i - 1][j] + 1 == min(cost[i - 1][j] + 1, cost[i][j - 1] + 1, cost[i - 1][j - 1] + sub_cost):
last_operation = Operation.DELETED
if cost[i][j - 1] + 1 == min(cost[i - 1][j] + 1, cost[i][j - 1] + 1, cost[i - 1][j - 1] + sub_cost):
last_operation = Operation.INSERTED
else:
last_operation = Operation.SUBSTITUTED
return cost[x][y], last_operation
The issue is that when you run cost[i][0] = i on an empty dictionary, you are trying assign a value to a sub-dictionary, but since you have not initialized any values in your dictionary yet, there is nothing to access, hence the 'Key Error'. You must initialize the sub-dictionary before you can add a key/value to it
for i in range(0, x):
cost[i] = {}
cost[i][0] = i
Or you could use defaultdict to set a default value of sub-items in your dictionary.

O(n) solution for finding maximum sum of differences python 3.x?

I was wondering, given a list of integers, say l, and if we are allowed to select 3 integers from this list, say left, middle, right, where middle > left, right and left, middle, right appear in that order in the list (ie. index(left)<index(middle)<index(right)), does there exist an O(n) solution for finding the maximum of middle - left + middle - right? You may suppose that lists that do not satisfy these conditions do not appear (eg. [5, 0, 5] as pointed out by Eric Duminil)
Currently, I am able to come up with what I believe is (roughly) an O(n^2) solution (correct me if I am wrong).
Essentially, my current idea is to do:
maximum = 0
for idx in range(1, N - 1):
left = min(l[0: idx])
right = min(l[idx + 1:])
middle = l[idx]
if left < middle and right < middle:
new_max = middle - left + middle - right
maximum = max(new_max, maximum)
Help/hints would be greatly appreciated.
Thanks!
You can run through your numbers once, keeping a running minimum value, and storing it at each step, so that at the end you know what the minimum value is to the left of each index.
That's O(n).
Similarly, you can run through all your numbers once from right to left, and work out what the minimum value is to the right of each index. That is O(n).
Then you can run through each possible middle value, and take the left and right values from your earlier computations. That is O(n).
O(n) + O(n) + O(n) = O(n).
Here's a way to calculate the minimums, left and right of every index, in O(n):
import random
N = 10
l = [random.randrange(N) for _ in range(N)]
print(l)
# => [9, 9, 3, 4, 6, 7, 0, 0, 7, 6]
min_lefts = []
min_left = float("inf")
min_rights = [None for _ in range(N)]
min_right = float("inf")
for i in range(N):
e = l[i]
if e < min_left:
min_left = e
min_lefts.append(min_left)
print(min_lefts)
# => [9, 9, 3, 3, 3, 3, 0, 0, 0, 0]
for i in range(N-1,-1,-1):
e = l[i]
if e < min_right:
min_right = e
min_rights[i] = min_right
print(min_rights)
# => [0, 0, 0, 0, 0, 0, 0, 0, 6, 6]
You can now iterate over every middle element in l (idx between 1 and N-2), and find the minimum of 2 * l[idx] - min_rights[idx] - min_lefts[idx]. This operation is also O(n):
print(max(2 * l[i] - min_rights[i] - min_lefts[i] for i in range(1, N-2)))
It outputs :
11
which is 2 * 7 - 0 - 3.
The trick is that minimum value of the list is always the part of solution (either left, or right).
Find minimum of the list, which is O(n). Now this minimum element will be either left or right.
Find maximum of (2x-y), where idx(x) > idx(y), and idx(x) < idx(min), that is check the left part of the list
Find max(2x-y), where idx(x) < idx(y), and idx(x) > idx(min), that is check the right part of the list
Now take maximum of the steps 2 and 3, which is your left/middle (or right/middle).
Here are some timings! Feel free to edit the code that does the timing and\add new entries.
from timeit import timeit
setup10 = '''
import numpy.random as nprnd
lst = list(nprnd.randint(1000, size=10))
'''
setup100 = '''
import numpy.random as nprnd
lst = list(nprnd.randint(1000, size=100))
'''
setup1000 = '''
import numpy.random as nprnd
lst = list(nprnd.randint(1000, size=1000))
'''
fsetup = '''
import sys
def f2(lst):
N = len(lst)
maximum = 0
for idx in range(1, N - 1):
left = min(lst[0: idx])
right = min(lst[idx + 1:])
middle = lst[idx]
if left < middle and right < middle:
new_max = middle - left + middle - right
maximum = max(new_max, maximum)
return maximum
def eric(lst):
N = len(lst)
min_lefts = []
min_left = float("inf")
min_rights = [None for _ in range(N)]
min_right = float("inf")
for i in range(N):
e = lst[i]
if e < min_left:
min_left = e
min_lefts.append(min_left)
for i in range(N-1,-1,-1):
e = lst[i]
if e < min_right:
min_right = e
min_rights[i] = min_right
return max(2 * lst[i] - min_rights[i] - min_lefts[i] for i in range(1, N-2))
def bpl(lst):
res = -sys.maxsize
a = sys.maxsize
b = -sys.maxsize
c = sys.maxsize
for i, v in enumerate(lst[1:-1]):
a = min(lst[i], a)
c = min(lst[i + 2], c)
b = max(lst[i], b)
res = max(2 * b - a - c, res)
return res
def meow(l):
N = len(l)
right_min = (N - 2) * [sys.maxsize]
right_min[0] = l[N - 1]
for i in range(3, N):
right_min[i - 2] = min(right_min[i - 2], l[N - i + 1])
left = l[2]
maximum = 2*l[1] - left - right_min[N - 3]
for idx in range(2, N - 1):
left = min(left, l[idx-1])
right = right_min[N - idx - 2]
middle = l[idx]
if left < middle and right < middle:
new_max = middle - left + middle - right
maximum = max(new_max, maximum)
return maximum
'''
print('OP with 10\t:{}'.format(timeit(stmt="f2(lst)", setup=setup10 + fsetup, number=100)))
print('eric with 10\t:{}'.format(timeit(stmt="eric(lst)", setup=setup10 + fsetup, number=100)))
print('bpl with 10\t:{}'.format(timeit(stmt="bpl(lst)", setup=setup10 + fsetup, number=100)))
print('meow with 10\t:{}'.format(timeit(stmt="meow(lst)", setup=setup10 + fsetup, number=100)))
print()
print('OP with 100\t:{}'.format(timeit(stmt="f2(lst)", setup=setup100 + fsetup, number=100)))
print('eric with 100\t:{}'.format(timeit(stmt="eric(lst)", setup=setup100 + fsetup, number=100)))
print('bpl with 100\t:{}'.format(timeit(stmt="bpl(lst)", setup=setup100 + fsetup, number=100)))
print('meow with 10\t:{}'.format(timeit(stmt="meow(lst)", setup=setup100 + fsetup, number=100)))
print()
print('OP with 1000\t:{}'.format(timeit(stmt="f2(lst)", setup=setup1000 + fsetup, number=100)))
print('eric with 1000\t:{}'.format(timeit(stmt="eric(lst)", setup=setup1000 + fsetup, number=100)))
print('bpl with 1000\t:{}'.format(timeit(stmt="bpl(lst)", setup=setup1000 + fsetup, number=100)))
print('meow with 10\t:{}'.format(timeit(stmt="meow(lst)", setup=setup1000 + fsetup, number=100)))
10 elements on the list, 100 repetitions
OP :0.00102
eric :0.00117
bpl :0.00141
meow :0.00159
100 elements on the list, 100 repetitions
OP :0.03200
eric :0.00654
bpl :0.01023
meow :0.02011
1000 elements on the list, 100 repetitions
OP :2.34821
eric :0.06086
bpl :0.10305
meow :0.21190
And as a bonus an inefficient one-liner:
maximum = max(2*z -sum(x) for x, z in zip([[min(lst[:i+1]), min(lst[i+2:])] for i, _ in enumerate(lst[:-2])], lst[1:-1]))
possible solution:
import sys
import random
random.seed(1)
l = [random.randint(0, 100) for i in range(10)]
print(l)
res = -sys.maxsize
a = sys.maxsize
b = -sys.maxsize
c = sys.maxsize
for i, v in enumerate(l[1:-1]):
a = min(l[i], a)
c = min(l[i + 2], c)
b = max(l[i], b)
res = max(2 * b - a - c, res)
print(res)
output:
[13, 85, 77, 25, 50, 45, 65, 79, 9, 2]
155
You are definitely on the right track, you just have to get rid of those min operations. So my hint for you is that you can pre-compute them (in linear time) beforehand, then look up the min in the loop, as you are already doing.
To clarify: you have to pre-compute min(list[0:i]) and min(list[i:n]) for all i's, before the part you already have. The idea is to store them in two arrays, say m1 and m2, such that m1[i] = min(list[0:i]) and m2[i] = min(list[i:n]). Then, use m1 and m2 in your loop
The challenge now is to compute m1 and m2 in linear time, meaning that you are not allowed to use the min function to compute them. If you have m1[i], how can you compute m1[i+1] using list[i+1]?

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

Arithmetic Sequences Slices in Python

I'm trying to write a function that takes a list of integers and finds all arithmetic sequences in it.
A = [-1, 1, 3, 3, 3, 2, 1, 0]
There are five arithmetic sequences in this list: (0, 2), (2,4), (4, 6), (4,7), (5,7) - these are indexes of first and last element of a sequence. A sequence is derived by the difference between elements.
As you see from the example above - the sequence must be longer than 2 elements (otherwise it would find a sequence between every two elements).
The function that I need to write must return the number of sequences it finds on the list - in this case it should return 5.
I'm kind of stuck - tried a few different approaches but failed miserably. The most recent thing I've done is:
def solution(A):
slices = []
x = 0
listlen = len(A)
while x < listlen-1:
print ("Current x:", x)
difference = A[x+1] - A[x]
#print ("1st diff: ", A[x+1], ",", A[x], " = ", difference)
for y in range(x+1, len(A)-1):
difference_2 = A[y+1] - A[y]
#print ("Next Items: ", A[y+1], A[y])
#print ("2nd diff: ", difference_2)
if (difference == difference_2):
#print ("I'm in a sequence, first element at index", x)
else:
#print ("I'm leaving a sequence, last element at index ", y)
slice = str(x) + "," + str(y)
slices.append(slice)
x += 1
#print ("Changing X to find new slice: x:", x)
break
print (slices)
I messed something up with iterating X, at this point in time, it's an endless loop.
Maybe you can use a logic like this -
>>> A = [-1, 1, 3, 3, 3, 2, 1, 0]
>>> def indices(l):
... res = []
... for i in range(0,len(l)-2):
... diff = l[i+1] - l[i]
... for j in range(i+2,len(l)):
... if (l[j] - l[j-1]) == diff:
... res.append((i,j))
... else:
... break;
... return res
...
>>> indices(A)
[(0, 2), (2, 4), (4, 6), (4, 7), (5, 7)]
a brute force approach is to just check each slice > len 3, for each slice you just need to subtract the first and last element to get the difference and see if all a[i+1] - A[i] are equal to the difference:
def is_arith(x):
return all(x[i + 1] - x[i] == x[1] - x[0]
for i in range(len(x) - 1))
def arith_count(A):
return sum(is_arith(A[i:j])for i in range(len(A))
for j in range(i + 3,len(A)+1))
A more efficient version:
def arith_sli(A):
n = len(A)
st,tot = 0, 0
while st < n - 2:
end = st + 1
dif = A[end] - A[st]
while end < n - 1 and A[end + 1] - A[end] == dif:
end += 1
ln = end - st + 1
if ln >= 3:
tot += (ln - 2) * (ln - 1) // 2
st = end
return tot
tot += (ln - 2) * (ln - 1) // 2 is the max number of slices that can be formed for any length progression >= 3, we set st = end because no progressions can overlap.
Both return the correct output, the latter is just considerably more efficient:
In [23]: A = [-1, 1, 3, 3, 3, 2, 1, 0]
In [24]: arith_sli(A)
Out[24]: 5
In [25]: arith_count(A)
Out[25]: 5
In [26]: A = [-1, 1, 3, 3, 4, 2, 1, 0,1,2]
In [27]: arith_sli(A)
Out[27]: 3
In [28]: arith_count(A)
Out[28]: 3

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