There are three types of foods were provided i.e. meat, cake and pizza
and N different stores selling it where, i can only pick one type of food from
each store. Also I can only buy items in A, B and C numbers where 'A' means, Meat from total 'A' number of different stores (see example). My task is
to consume food, so that i can have maximum amount of energy.
example,
10 <= number of stores <br>
5 3 2 <= out of 10 stores I can pick meat from 5 stores only. Similarly,
I can pick cake from 3 out of 10 stores...
56 44 41 1 <= Energy level of meat, cake and pizza - (56, 44, 41) for first store.<br>
56 84 45 2
40 98 49 3
91 59 73 4
69 94 42 5
81 64 80 6
55 76 26 7
63 24 22 8
81 60 44 9
52 95 11 10
So to maximize my energy, I can consume...
Meat from store numbers:
[1, 4, 7, 8, 9] => [56, 91, 55, 63, 81]
Cake from store numbers:
[3, 5, 10] => [98, 94, 95]
Pizza from store numbers:
[2, 6] => [45, 80]
This leads me to eventually obtain a maximum energy level of 758.
As I am new to dynamic programming, I tried to solve it by generating unique combinations like,
10C5 * (10-5)C3 * (10-5-3)C2 = 2520
Here is my code,
nStores = 10
a, b, c = 5, 3, 2
matrix = [
[56,44,41],
[56,84,45],
[40,98,49],
[91,59,73],
[69,94,42],
[81,64,80],
[55,76,26],
[63,24,22],
[81,60,44],
[52,95,11]
]
count = a + b + c
data = []
allOverCount = [i for i in range(count)]
def genCombination(offset, depth, passedData, reductionLevel = 3):
if (depth == 0):
first = set(data)
if reductionLevel == 3:
return genCombination(0,b,[i for i in allOverCount if i not in first], reductionLevel=2)
elif reductionLevel == 2:
return genCombination(0,c,[i for i in allOverCount if i not in first], reductionLevel=1)
elif reductionLevel == 1:
xAns = 0
for i in range(len(data)):
if i < a:
xAns += matrix[data[i]][0]
elif i < a + b:
xAns += matrix[data[i]][1]
else:
xAns += matrix[data[i]][2]
return xAns
oneData = 0
for i in range(offset, len(passedData) - depth + 1 ):
data.append(passedData[i])
oneData = max(oneData, genCombination(i+1, depth-1, passedData, reductionLevel))
del data[-1]
return oneData
passedData = [i for i in range(count)]
finalOutput = genCombination(0,a,passedData)
print(finalOutput)
I know this is not the right way to do it. How can I optimize it?
This is a solution using Linear Programming through pulp (https://pypi.org/project/PuLP) that gives me the optimal solution
Maximum energy level: 758.0
Mapping of stores per foodtype: {1: [9, 2, 4], 0: [3, 8, 0, 6, 7], 2: [1, 5]}
The performance should be better than a hand-coded exhaustive solver I think.
from collections import defaultdict
import pulp
# data
nStores = 10
a, b, c = max_stores = 5, 3, 2
matrix = [
[56, 44, 41],
[56, 84, 45],
[40, 98, 49],
[91, 59, 73],
[69, 94, 42],
[81, 64, 80],
[55, 76, 26],
[63, 24, 22],
[81, 60, 44],
[52, 95, 11]
]
# create an LP problem
lp = pulp.LpProblem("maximize energy", sense=pulp.LpMaximize)
# create the list of indices for the variables
# the variables are binary variables for each combination of store and food_type
# the variable alpha[(store, food_typeà] = 1 if the food_type is taken from the store
index = {(store, food_type) for store in range(nStores) for food_type in range(3)}
alpha = pulp.LpVariable.dicts("alpha", index, lowBound=0, cat="Binary")
# add the constrain on max stores
for food_type, n_store_food_type in enumerate(max_stores):
lp += sum(alpha[(store, food_type)] for store in range(nStores)) <= n_store_food_type
# only one food type can be taken per store
for store in range(nStores):
lp += sum(alpha[(store, food_type)] for food_type in range(3)) <= 1
# add the objective to maximise
lp += sum(alpha[(store, food_type)] * matrix[store][food_type] for store, food_type in index)
# solve the problem
lp.solve()
# collect the results
stores_for_foodtype = defaultdict(list)
for (store, food_type) in index:
# check if the variable is active
if alpha[(store, food_type)].varValue:
stores_for_foodtype[food_type].append(store)
print(f"Maximum energy level: {lp.objective.value()}")
print(f"Mapping of stores per foodtype: {dict(stores_for_foodtype)}")
It looks like a modification to knapsack would solve it.
let's define our dp table as 4-dimensional array dp[N+1][A+1][B+1][C+1]
now some cell dp[n][a][b][c] means that we have considered n shops, out of them we picked a shops for meat,
b shops for cake and c shops for pizza and it stores max energy we can have.
Transitions are easy too, from some state dp[n][a][b][c] we can move to:
dp[n+1][a][b][c] if we skip n+1 th shop
dp[n+1][a+1][b][c] if we buy
meat from shop n+1
dp[n+1][a][b+1][c] if we buy cake from shop n+1
dp[n+1][a][b][c+1] if we buy pizza from shop n+1
All that's left is to fill dp table. Sample code:
N = 10
A,B,C = 5,3,2
energy = [
[56, 44, 41],
[56, 84, 45],
[40, 98, 49],
[91, 59, 73],
[69, 94, 42],
[81, 64, 80],
[55, 76, 26],
[63, 24, 22],
[81, 60, 44],
[52, 95, 11]
]
dp = {}
for n in range(N+1):
for a in range(A+1):
for b in range(B+1):
for c in range(C+1):
dp[n,a,b,c]=0
answer = 0;
for n in range(N+1):
for a in range(A+1):
for b in range(B+1):
for c in range(C+1):
#Case 1, skip n-th shop
if (n+1,a,b,c) in dp: dp[n+1,a,b,c] = max(dp[n+1,a,b,c], dp[n,a,b,c])
#Case 2, buy meat from n-th shop
if (n+1,a+1,b,c) in dp: dp[n+1,a+1,b,c] = max(dp[n+1,a+1,b,c], dp[n,a,b,c] + energy[n][0])
#Case 3, buy cake from n-th shop
if (n+1,a,b+1,c) in dp: dp[n+1,a,b+1,c] = max(dp[n+1,a,b+1,c], dp[n,a,b,c] + energy[n][1])
#Case 4, buy pizza from n-th shop
if (n+1,a,b,c+1) in dp: dp[n+1,a,b,c+1] = max(dp[n+1,a,b,c+1], dp[n,a,b,c] + energy[n][2])
answer = max(answer,dp[n,a,b,c])
print(answer)
Related
I have two lists of marks for the same set of students. For example:
A = [22, 2, 88, 3, 93, 84]
B = [66, 0, 6, 33, 99, 45]
If I accept only students above a threshold according to list A then I can look up their marks in list B. For example, if I only accept students with at least a mark of 80 from list A then their marks in list B are [6, 99, 45].
I would like to compute the smallest threshold for A which gives at least 90% of students in the derived set in B getting at least 50. In this example the threshold will have to be 93 which gives the list [99] for B.
Another example:
A = [3, 36, 66, 88, 99, 52, 55, 42, 10, 70]
B = [5, 30, 60, 80, 80, 60, 45, 45, 15, 60]
In this case we have to set the threshold to 66 which then gives 100% of [60, 80, 80, 60] getting at least 50.
This is an O(nlogn + m) approach (due to sorting) where n is the length of A and m is the length of B:
from operator import itemgetter
from itertools import accumulate
def find_threshold(lst_a, lst_b):
# find the order of the indices of lst_a according to the marks
indices, values = zip(*sorted(enumerate(lst_a), key=itemgetter(1)))
# find the cumulative sum of the elements of lst_b above 50 sorted by indices
cumulative = list(accumulate(int(lst_b[j] > 50) for j in indices))
for i, value in enumerate(values):
# find the index where the elements above 50 is greater than 90%
if cumulative[-1] - cumulative[i - 1] > 0.9 * (len(values) - i):
return value
return None
print(find_threshold([22, 2, 88, 3, 93, 84], [66, 0, 6, 33, 99, 45]))
print(find_threshold([3, 36, 66, 88, 99, 52, 55, 42, 10, 70], [5, 30, 60, 80, 80, 60, 45, 45, 15, 60]))
Output
93
66
First, define a function that will tell you if 90% of students in a set scored more than 50:
def setb_90pc_pass(b):
return sum(score >= 50 for score in b) >= len(b) * 0.9
Next, loop over scores in a in ascending order, setting each of them as the threshold. Filter out your lists according that threshold, and check if they fulfill your condition:
for threshold in sorted(A):
filtered_a, filtered_b = [], []
for ai, bi in zip(A, B):
if ai >= threshold:
filtered_a.append(ai)
filtered_b.append(bi)
if setb_90pc_pass(filtered_b):
break
print(threshold)
I am starting to learn python for a thesis project and here I am looking to get an output array using a conditional statement taking elements from 3 different arrays and need some guidance on how to approach this:
# I have 3 lists here with readings from a sensor:
summer = [92, 99, 86]
autumn = [91, 98, 82]
winter = [93, 96, 83]
# I want to perform this conditional statements on the arrays
# using the 1st element of each arrays, then the 2nd element
# and lastly the 3rd elements from each array:
if summer >= 90 and autumn >= 90 and winter >= 90:
a = 1
elif summer >= 90 and autumn >= 90 and winter >= 70:
a = 2
elif summer >= 50 and autumn >= 50 and winter >= 50:
a = 3
else:
a = 4
So in this example I am looking for a singular overall output of a = [1,1,3]
can anyone help with how I should approach this?
Use a for loop to iterate through the lists:
summer = [92, 99, 86]
autumn = [91, 98, 82]
winter = [93, 96, 83]
out = []
for s, a, w in zip(summer, autumn, winter):
if s >= 90 and a >= 90 and w >= 90:
out.append(1)
elif s >= 90 and a >= 90 and w >= 70:
out.append(2)
elif s >= 50 and a >= 50 and w >= 50:
out.append(3)
else:
out.append(4)
print(out) # [1, 1, 3]
You could use indexing to access the contents of each array/list:
summer = [92,99,86]
autumn = [91,98,82]
winter = [93,96,83]
a = []
#range(3) since 3 is the length of each array
for i in range(3):
if summer[i]>=90 and autumn[i]>=90 and winter[i]>=90:
n=1
elif summer[i]>=90 and autumn[i]>=90 and winter[i]>=70:
n=2
elif summer[i]>=50 and autumn[i]>=50 and winter[i]>=50:
n=3
else:
n=4
a.append(n)
print(a)
I prefer a "table driven" approach to this kind of problem rather than encumbering the logic with "magic" numbers. Something like this:
summer = [92, 99, 86]
autumn = [91, 98, 82]
winter = [93, 96, 83]
cv = [(90, 90, 90), (90, 90, 70), (50, 50, 50)]
av = []
for t in zip(summer, autumn, winter):
for i, c in enumerate(cv, 1):
if all(m >= n for m, n in zip(t, c)):
av.append(i)
break
else:
av.append(i+1)
print(av)
Output:
[1, 1, 3]
The advantage of this is that the core functionality doesn't change. You just need to change the lists (summer, autumn, winter & cv) appropriately
I am practicing some exercises and have been going in circles trying to figure it out. The first part is to create a 5x5 matrix with NumPy giving it random values, and I’ve already solved it.
Now I need to see if the matrix, either horizontally or vertically, has consecutive numbers
(for example: The matrix of the attached image does not have consecutive numbers).
Here is there are 4 consecutive numbers in the first column:
[[50 92 78 84 36]
[51 33 94 73 32]
[52 94 35 47 9]
[53 5 60 55 67]
[83 51 56 99 18]]`
Here are 4 consecutive numbers in the last row
[[50 92 78 84 36]
[41 33 94 73 32]
[72 94 35 47 9]
[55 5 60 55 67]
[84 85 86 87 18]]"
The last step is to continue randomizing the array until you find those consecutive numbers.
Here is a naive approach to check whether each row/column of a given matrix has a given amount (4 in this case) of consecutive numbers:
import numpy as np
def has_consecutive_number(M, num_consecutive=4):
for v in np.vstack((M, M.T)): # You need to check both columns and rows
count = 1 # Record how many consecutive numbers found
current_num = 0 # Recording 1 or -1
for i in range(1, len(v)):
diff = v[i] - v[i-1]
if diff == 1: # if diff is 1
if current_num != 1: # if previous diff is not 1, reset counter
count = 1
current_num = 1
count += 1
elif diff == -1:
if current_num != -1: count = 1
current_num = -1
count += 1
else: # reset counter
current_num = 0
count = 1
if count == num_consecutive:
return True
return False
M1 = np.array([ [10, 43, 74, 32, 69],
[20, 19, 69, 83, 8],
[89, 31, 62, 61, 17],
[35, 3, 77, 22, 29],
[52, 59, 86, 55, 73] ])
print(has_consecutive_number(M1, 4))
M2 = np.array([ [10, 43, 74, 32, 69],
[20, 19, 69, 83, 8],
[89, 31, 62, 61, 17],
[35, 3, 77, 22, 29],
[52, 53, 54, 55, 73] ])
print(has_consecutive_number(M2, 4))
Output is False for first matrix and True for second matrix
False
True
I'm working on a time-series problem, and I have a list of events such that each data point represent several objects being pulled from an inventory.
Each time the value reaches below some threshold, I want to add a constant number to the inventory.
For example, I want:
(threshold = 55, constant = 20)
70 60 50 45 30 0 -5 -75
to become:
70 60 70 65 70 60 75 25
Is there a "pythonic" way (pandas, numpy, etc...) to do it with no loops?
Edit: the addition of constant can occur multiple times, and only effect the future (i.e indexes that are greater than the observed index). This is the code I'm using right now, and my goal is to lose the for loop:
threshold = 55
constant = 20
a = np.array([70, 60, 50, 45, 30, 0, -5, -75])
b = a.copy()
for i in range(len(b)):
if b[i] <= threshold:
temp_add_array = np.zeros(b.shape)
indexes_to_add = np.array(range(len(b))) >= i
temp_add_array[indexes_to_add] += constant
b += temp_add_array.astype(int)
print(b)
print('*************')
print('[70 60 70 65 70 60 75 25]')
Since you're allowing for numpy:
>>> import numpy as np
# threshold and constant
>>> t, c = 55, 20
>>> data = np.asarray([70, 60, 50, 45, 30, 0, -5, -75])
# if you allow for data == threshold
>>> np.where(data >= t, data, data + c*((t-1-data) // c + 1))
array([70, 60, 70, 65, 70, 60, 55, 65])
# if you enforce data > threshold
>>> np.where(data > t, data, data + c*((t-data) // c + 1))
array([70, 60, 70, 65, 70, 60, 75, 65])
But there is really no need for an external dependency for a task like that
# threshold and constant
>>> t, c = 55, 20
>>> data = [70, 60, 50, 45, 30, 0, -5, -75]
# if you allow for data == threshold
>>> [x if x >= t else x + c*((t-1-x)//c + 1) for x in data]
[70, 60, 70, 65, 70, 60, 55, 65]
# if you enforce data > threshold
>>> [x if x > t else x + c*((t-x)//c + 1) for x in data]
[70, 60, 70, 65, 70, 60, 75, 65]
Edit of OP
I doubt there's a (readable) solution for your problem without using a loop; best thing I could come up with:
>>> import numpy as np
>>> a = np.asarray([70, 60, 50, 45, 30, 0, -5, -75])
# I don't think you *can* get rid of the loop since there are forward dependencies in the the data
>>> def stock_inventory(data: np.ndarray, threshold: int, constant: int) -> np.ndarray:
... res = data.copy()
... for i, e in enumerate(res):
... if e <= threshold:
... res[i:] += constant
... return res
...
>>> stock_inventory(a, threshold=55, constant=20)
array([70, 60, 70, 65, 70, 60, 75, 25])
Assuming a numpy ndarray...
original array is named a
subtract the threshold value from a - name the result b
make a boolean array of b < 0 - name this array c
integer/floor divide b by -1 * constant - name this d (it could be named b as it is no longer needed)
add one to d - name this e
use c as a boolean index to add e to a for those values that were less than the threshold. a[c] += e[c]
https://projecteuler.net/problem=18
Given a triangle of integers, the problem is to find the maximum path sum from top to bottom (where all the numbers in the path must be adjacent).
I had an idea for an algorithm: starting at the very top, calculate the sum of the left and right paths (left all the way down, and right all the way down), if the left sum is greater, jump to the left-adjacent number, if the right sum is greater, jump to the right-adjacent number, repeat the algorithm starting from the current number, and so on until you get to the bottom row.
triangle = ['75', '9564', '174782', '18358710', '2004824765', '190123750334', '88027773076367', '9965042806167092', '414126568340807033', '41487233473237169429', '5371446525439152975114', '701133287773177839681757', '91715238171491435850272948', '6366046889536730731669874031', '046298272309709873933853600423']
maximumPath = [75]
maxSum = 75 #Start it with the starting element of the triangle.
def triNum(row, index): #Returns the number at given row, number in row
return(int(triangle[row][2*index:2*(index+1)])) #Nota bene: returns an integer.
def options(row, index): #Rows start at 0, index starts at 0
return(triNum(row+1, index), triNum(row+1, index+1))
def criticalPathSum(startRow, startIndex, direction):
critPath = []
if direction == 'left':
directionNum = 0
else:
directionNum = 1
sum = triNum(startRow, startIndex) #Starting sum of left and right paths is just the number at the start of both paths.
for i in range(startRow + 1, len(triangle)):
startIndex += directionNum
sum += triNum(i, startIndex)
critPath.append(triNum(i, startIndex))
#print(triNum(i, startIndex + directionNum))
return(sum, critPath)
pathIndex = 0
for row in range(0, len(triangle)-1):
print('These are my options: ' + str(options(row, pathIndex)))
print('Left Sum: ' + str(criticalPathSum(row, pathIndex, 'left')) + ', ' + 'Right Sum: ' + str(criticalPathSum(row, pathIndex, 'right')))
if criticalPathSum(row, pathIndex, 'left') > criticalPathSum(row, pathIndex, 'right'):
maximumPath.append(triNum(row + 1, pathIndex))
print('Left. ' + str(triNum(row + 1, pathIndex)))
else:
print('Right. ' + str(triNum(row + 1, pathIndex + 1)))
pathIndex += 1
maximumPath.append(triNum(row + 1, pathIndex))
maxSum += triNum(row + 1, pathIndex)
print('_______________________________')
print('\n')
print(maximumPath)
print(maxSum)
The answer is 1067, but I get 883. This is the maximum path, according to the algorithm:
[75, 95, 17, 35, 82, 75, 7, 16, 80, 37, 91, 17, 91, 67, 98].
Your algorithm is wrong: in a triangle like
1
1 4
1 4 1
9 1 4 1
it is too tempted by the 9 (always going left) to see the optimal 1+4+4+4 = 13 route.
This is one of the many wrong algorithms that the test data was designed to defeat. You chose what is called a "greedy" algorithm, one that takes the maximum value for any single step, without considering long-term characteristics of the problem.
Rather, you need to work from the bottom of the triangle upward. At each juncture, note which of the two paths gives the maximum sum to the bottom of the triangle, and record that as the optimum result from that node. When you hit the apex, you will have the desired answer as the larger of the two elements underneath.
For instance, given the triangle
1
2 1
2 1 9
1 2 1 9
your algorithm will be greedy and take the path 1-2-2-2; the lower choice of a 1 in row two cuts off that branch from the short-sighted logic.
Rather, you need to build up the totals from the bottom, taking the best of two paths at each node:
20
6 19
4 3 18
1 2 1 9
In case this isn't clear enough ...
the bottom row has no further choices; each value is its own best path to the end. For the row above, let's go right to left, considering each value and its two "children":
2 1 9
1 2 1 9
The 2 has two values below, 1 and 2. Obviously, the 2 is better, so the best path from there is 2+2 = 4.
The 1 likewise has a 2 and 1 below; again, the better path is 1+2, giving 3.
The 9 has children of 1 and 9; we choose 9+9=18. The rows now appear as
1
2 1
4 3 18
1 2 1 9
Now we move up one row, cosindering the two choices for 2 1 there.
The 2 has 4 and 3; the 1 has 3 and 18. Again taking the higher value in each case and adding the node value, we get 2+4 = 6 and 1+18 = 19:
1
6 19
4 3 18
1 2 1 9
Finally, the top node chooses the larger value of 19, giving a total of 20 along the path 1-1-9-9.
Got it?
listOfLists = [
[75],
[95, 64],
[17, 47, 82],
[18, 35, 87, 10],
[20, 4, 82, 47, 65],
[19, 1, 23, 75, 3, 34],
[88, 2, 77, 73, 7, 63, 67],
[99, 65, 4, 28, 6, 16, 70, 92],
[41, 41, 26, 56, 83, 40, 80, 70, 33],
[41, 48, 72, 33, 47, 32, 37, 16, 94, 29],
[53, 71, 44, 65, 25, 43, 91, 52, 97, 51, 14],
[70, 11, 33, 28, 77, 73, 17, 78, 39, 68, 17, 57],
[91, 71, 52, 38, 17, 14, 91, 43, 58, 50, 27, 29, 48],
[63, 66, 4, 68, 89, 53, 67, 30, 73, 16, 69, 87, 40, 31],
[4, 62, 98, 27, 23, 9, 70, 98, 73, 93, 38, 53, 60, 4, 23]]
for i in range(len(listOfLists) - 2, -1, -1):
for j in range(len(listOfLists[i])):
listOfLists[i][j] += max(listOfLists[i+1][j], listOfLists[i+1][j+1])
print(listOfLists[0][0])
The bottom - top approach and it gets the right answer according to PE.
Thanks!