Greedy algorithm in Python - python

I want c in plunder(aList, c) to equal 0.
List = [('Gold', 10, 500), ('Silver', 5, 200), ('Diamond', 2, 2000), ('Platinum', 20, 1000)]
aList = sorted(List, key = lambda x : x[2]) # sort the list above
Gives me the sorted list based on the 3rd value of each tuple. So I get:
[('Silver', 5, 200), ('Gold', 10, 500), ('Platinum', 20, 1000), ('Diamond', 2, 2000)]
I am trying to get the plunder(aList, c) to keep subtracting the middle values of each tuple (2, 20, 10, 5) from c until c = 0.
Here is my code:
List = [('Gold', 10, 500), ('Silver', 5, 200), ('Diamond', 2, 2000), ('Platinum', 20, 1000)]
aList = sorted(List, key = lambda x : x[2]) # sort the list above
def plunder(aList, c):
aList[-1] = list(aList[-1])
i = aList[-1]
r = 0
if c > 0 and i[1] != 0:
c -= 1
i[1] -=1
r += 1
return plunder(aList, c-r)
elif c == 0:
pass
print('Done')
else:
return plunder(aList[:-1], c-r)
plunder(aList, 10)
But when I run it, it prints done and the new list is:
[('Silver', 5, 200), ('Gold', 10, 500), ('Platinum', 20, 1000), ['Diamond', 0, 2000]]
and also when I type c in the python shell, it tells me that c is not defined. How could I fix these issues?
So if the c value is 10. My expected output would be:
[('Silver', 5, 200), ('Gold', 10, 500), ['Platinum', 12, 1000], ['Diamond', 0, 2000]]
I subtracted as many diamonds as I could from the 10 (10 - 2 = 8) so 0 diamonds were left. Then I subtracted 8 from 20 platinums and the weight of the platinums changed to 12 (since I took 8 platinums. And now my c ('capacity') is 0. 2 diamonds + 8 platinums = 10 (which was my c).

The main problem is that you are relying on Python's pass by reference for lists to modify the list in place.
That works fine initially, but when you reach
plunder(aList[:-1], c-r)
Python creates a copy of the list and proceeds to modify that copy. Thus your original list remains unchanged after Diamonds are exhausted (you hit the else part).
Note that you can see this behaviour in your printed aList as only last entry is a list and all other are tuples.
[
('Silver', 5, 200),
('Gold', 10, 500),
('Platinum', 20, 1000),
['Diamond', 0, 2000] #Only list
]
If you add a print alist[-1] statement to the function, you can see it even more clearly.
['Diamond', 2, 2000]
['Diamond', 1, 2000]
['Diamond', 0, 2000]
['Platinum', 20, 1000]
['Platinum', 19, 1000]
['Platinum', 18, 1000]
['Platinum', 17, 1000]
['Platinum', 16, 1000]
['Platinum', 15, 1000]
['Platinum', 14, 1000]
['Platinum', 13, 1000]
['Platinum', 12, 1000]
So your algorithm does work but as you have no way of keeping the result, it does not (fully) affect your original list.

Here is what I think is a more simple approach. Simply iterate through the available treasure and grab as much as you can/need from each one.
def demo():
units_to_plunder = 10
treasure = new_treasure_pile()
plundered_units = plunder_units(treasure, units_to_plunder)
print treasure
print plundered_units
units_to_plunder = 1000
treasure = new_treasure_pile()
plundered_units = plunder_units(treasure, units_to_plunder)
print treasure
print plundered_units
def plunder_units(treasure, total_plunder_units):
treasure_sorted = list(sorted(treasure, key=lambda t: t[2], reverse=True))
plundered = list()
for treasure_type in treasure_sorted:
# stop condition when desired units taken
if total_plunder_units <= 0:
break
t_units_to_take = min(total_plunder_units, treasure_type[1])
# update the 3 moving parts
treasure_type[1] -= t_units_to_take
plundered.append([treasure_type[0], t_units_to_take, treasure_type[2]])
total_plunder_units -= t_units_to_take
return plundered
def new_treasure_pile():
return [['Gold', 10, 500],
['Silver', 5, 200],
['Diamond', 2, 2000],
['Platinum', 20, 1000]]
if __name__ == '__main__':
demo()
Output for c=10 (I think as you expect)
[['Gold', 10, 500], ['Silver', 5, 200], ['Diamond', 0, 2000], ['Platinum', 12, 1000]]
[['Diamond', 2, 2000], ['Platinum', 8, 1000]]
Output for c=1000 (take it all)
[['Gold', 0, 500], ['Silver', 0, 200], ['Diamond', 0, 2000], ['Platinum', 0, 1000]]
[['Diamond', 2, 2000], ['Platinum', 20, 1000], ['Gold', 10, 500], ['Silver', 5, 200]]

Related

Identify index of all elements in a list comparing with another list

For instance I have a list A:
A = [100, 200, 300, 200, 400, 500, 600, 400, 700, 200, 500, 800]
And I have list B:
B = [100, 200, 200, 500, 600, 200, 500]
I need to identify the index of elements in B with comparison to A
I have tried:
list_index = [A.index(i) for i in B]
It returns:
[0, 1, 1, 5, 6, 1, 5]
But what I need is:
[0, 1, 3, 5, 6, 9, 10]
How can I solve it?
You can iterate through the enumeration of A to keep track of the indices and yield the values where they match:
A = [100,200,300,200,400,500,600,400,700,200,500,800]
B = [100,200,200,500,600,200,500]
def get_indices(A, B):
a_it = enumerate(A)
for n in B:
for i, an in a_it:
if n == an:
yield i
break
list(get_indices(A, B))
# [0, 1, 3, 5, 6, 9, 10]
This avoids using index() multiple times.
You can try something like this. Move over both lists and append the index when they are equal:
A = [100,200,300,200,400,500,600,400,700,200,500,800]
B = [100,200,200,500,600,200,500]
i, j = 0, 0
list_index = []
while j < len(B):
if B[j] == A[i]:
list_index.append(i)
j += 1
i += 1
print(list_index)
Output:
[0, 1, 3, 5, 6, 9, 10]
You can create a list called indices, and get the first index into it. Then iterate rest of the items in B, then take the slice of A from the last index in indices list and get the index of the item, add it to the last index + 1, then append it back to the indices list.
indices = [A.index(B[0])]
for i,v in enumerate(B[1:]):
indices.append(A[indices[-1]+1:].index(v)+indices[-1]+1)
#indices: [0, 1, 3, 5, 6, 9, 10]
Here's what I would use:
A=[100,200,300,200,400,500,600,400,700,200,500,800]
B=[100,200,200,500,600,200,500]
list_index = []
removedElements = 0
for i in B:
indexInA = A.index(i)
A.pop(indexInA)
list_index.append(indexInA+removedElements)
removedElements+=1
print(list_index)
A = np.array([100,200,300,200,400,500,600,400,700,200,500,800])
B = [100, 200, 200, 500, 600, 200, 500]
idx = np.arange(len(A))
indices = {i: idx[A == i].tolist() for i in set(B)}
[indices[i].pop(0) for i in B]
I loop through B and set the checked index to None in A. Thus the code will alter A.
A = [100, 200, 300, 200, 400, 500, 600, 400, 700, 200, 500, 800]
B = [100, 200, 200, 500, 600, 200, 500]
res = []
for i in B:
res.append(A.index(i))
A[A.index(i)] = None
print(res)
Output:
[0, 1, 3, 5, 6, 9, 10]

How to get index of the max element from two lists after sorting it?

I have three lists price1, price2 and deviation. I wanted to find the top three highest price between price1 and price2. So to achieve that I first sorted them in decreasing order then I took the first 3 elements from both the list price1 and price2, and after that, I found the max between each max. Now I want to know the original position of the max value that is obtained after all this so that I can put it in deviation. Before sorting price1 and price2:
price1 = [12,1,2,4,45,67,433,111,45,12] #Total 10 elements in all the lists
price2 = [23,12,233,11,54,232,323,12,42,4]
deviation = [23,45,56,67,78,786,45,34,2,1]
After sorting to get the top 3 prices:
print("Price1: ",sorted(price1, reverse=True))
print("Price2: ",sorted(price2, reverse=True))
output:
Price1: [433, 111, 67]
Price2: [323, 233, 232]
To get the max from it:
sortedPrice1 = sorted(price1, reverse=True)
sortedPrice2 = sorted(price2, reverse=True)
print("Price1: ", sortedPrice1[:3])
print("Price2: ", sortedPrice2[:3])
for i,j in zip(sortedPrice1[:3], sortedPrice2[:3]):
print("max: ", max(i, j))
output:
Price1: [433, 111, 67]
Price2: [323, 233, 232]
max: 433
max: 233
max: 232
Now What I want is that I want to find the postions of these max values. for example:
433 is in `price1` at 6th position
233 is in `price2` at 2nd position
232 is in `price2` at 5th position
and ultimately in the end I want to put these positions into the deviation list to get the value in front of these prices. so:
deviation[6] = 45
deviations[2] = 56
deviations[5] = 786
No need to use list.index(), zip() is enough:
price1 = [12,1,2,4,45,67,433,111,45,12] #Total 10 elements in all the lists
price2 = [23,12,233,11,54,232,323,12,42,4]
deviation = [23,45,56,67,78,786,45,34,2,1]
for a, b, _ in zip( sorted(zip(price1, deviation), key=lambda k: k[0], reverse=True),
sorted(zip(price2, deviation), key=lambda k: k[0], reverse=True), range(3) ): # range(3) because we want max 3 elements
print(max(a, b, key=lambda k: k[0]))
Prints:
(433, 45)
(233, 56)
(232, 786)
EDIT: To have sublists in price1/price2/deviation lists, you can do:
price1 = [[12, 2, 3, 4],[1, 2, 5, 56],[12,34,45,3],[23,2,3,4],[1,6,55,34]]
price2 = [[1, 2, 3, 4],[1, 2, 3, 4],[1, 2, 3, 4],[1, 2, 3, 4],[1, 2, 3, 4]]
deviation = [[10, 20, 30, 40],[10, 20, 30, 40],[10, 20, 30, 40],[10, 20, 30, 40],[10, 20, 30, 40]]
for p1, p2, dev in zip(price1, price2, deviation):
print('price1=', p1)
print('price2=', p2)
print('dev=', dev)
for a, b, _ in zip( sorted(zip(p1, dev), key=lambda k: k[0], reverse=True),
sorted(zip(p2, dev), key=lambda k: k[0], reverse=True), range(3) ): # range(3) because we want max 3 elements
print(max(a, b, key=lambda k: k[0]))
print()
Prints:
price1= [12, 2, 3, 4]
price2= [1, 2, 3, 4]
dev= [10, 20, 30, 40]
(12, 10)
(4, 40)
(3, 30)
price1= [1, 2, 5, 56]
price2= [1, 2, 3, 4]
dev= [10, 20, 30, 40]
(56, 40)
(5, 30)
(2, 20)
price1= [12, 34, 45, 3]
price2= [1, 2, 3, 4]
dev= [10, 20, 30, 40]
(45, 30)
(34, 20)
(12, 10)
price1= [23, 2, 3, 4]
price2= [1, 2, 3, 4]
dev= [10, 20, 30, 40]
(23, 10)
(4, 40)
(3, 30)
price1= [1, 6, 55, 34]
price2= [1, 2, 3, 4]
dev= [10, 20, 30, 40]
(55, 30)
(34, 40)
(6, 20)
The list method index returns the position of an element in a list. If you replace your max function with an if statement, you can produce the desired output
for i,j in zip(sortedPrice1[:3], sortedPrice2[:3]):
if i > j:
print("%d is in `price1` at the %dth position" % (i, price1.index(i))
else:
print("%d is in `price2` at the %dth position" % (j, price2.index(j))
Note that this code:
Doesn't deal with the case i=j
Doesn't deal with multiple members of price1/price2 which are both in the top three.
You can do it with a try / except too but with the same limitations as #Yberman states in his answer but without needing to define any other variable than the lists itself:
price1 = [12,1,2,4,45,67,433,111,45,12] #Total 10 elements in all the lists
price2 = [23,12,233,11,54,232,323,12,42,4]
deviation = [23,45,56,67,78,786,45,34,2,1]
for i,j in zip(sorted(price1,reverse=True)[:3], sorted(price2,reverse=True)[:3]):
try:
print(deviation[price1.index(max(i, j))])
except ValueError:
print(deviation[price2.index(max(i,j))])
Output:
45
56
786
First create a copy of all 3 lists such as,
price1 = [12,1,2,4,45,67,433,111,45,12] #Total 10 elements in all the lists
price2 = [23,12,233,11,54,232,323,12,42,4]
deviation = [23,45,56,67,78,786,45,34,2,1]
d_p1=price1
d_p2=price2
d_dev=deviation
then perform all the operation to get max elements on this duplicate lists,
and then when you have max elements in a list such as,
max_l=[433,233,232]
traverse through this list to get position,
for i in max_l:
if i in price_1:
print("index:",price_1.index(i))
else:
print("index:",price_2.index(i))
if the positions of elements are changing after performing some operations then this process can be helpful to find original positions.
You can do it it two lines of code.
First find the index.
item_index_in_original_list = price1.index(433)
Then use it in your deviation list
deviation_of_max_item = deviation[item_index_in_original_list]
To repeat this for other items you can make a function with these two lines and call it.
def find_deviation(item_value,original_list):
item_index_in_original_list = original_list.index(item_value)
return deviation[item_index_in_original_list]
required_deviation = find_deviation(433, price1)

Overwrite a list value if a certain condition for another list is true (python)

Unfortunately I was not able to be more specific with my title, but an example should make my problem clear. I have two lists f and a and if f[i] is equal to f[i-1]. I would like to have a new entry in f which is equal to f[i] and f[i-1] (obviously) and overwrites both of them. In a I would like to have to a_new = a[i] + a[i-1] replacing a[i] and a[i-1].
f = [10, 25, 50, 50, 75, 100, 1000, 1000, 1100, 1100]
a = [1, 3, 2, 4, 5, 3, 10, 15, 5, 5]
My desired output is:
f = [10, 25, 50, 75, 100, 1000, 1100]
a = [1, 3, 6, 5, 3, 25, 10]
I believe that some sort of list comprehension would be the optimal approach, but I couldn't figure it out yet. I doesn't need to be a list comprehension though, I am happy as long as there is some solution.
You can zip the lists together, use groupby to group the pairs by the f values, then sum the a values for each group. Then you just need to unzip them back into separate lists
from itertools import groupby
from operator import itemgetter
groups = groupby(zip(f, a), key=itemgetter(0))
f_a_generator = ((k, sum(map(itemgetter(1), pairs))) for k, pairs in groups)
f1, a1 = zip(*f_a_generator) # map(list, ...) If you need them as lists
print(f1, a1, sep='\n')
# (10, 25, 50, 75, 100, 1000, 1100)
# (1, 3, 6, 5, 3, 25, 10)
To answer your question in the comments, you can change the line
sum(map(itemgetter(1), pairs)))
to call some function other than sum:
def logarithmic_sum(values):
return 10*np.log10(sum((10**(val/10)) for val in values))
groups = groupby(zip(f, a), key=itemgetter(0))
f_a_generator = ((k, logarithmic_sum(map(itemgetter(1), pairs))) for k, pairs in groups)
f1, a1 = zip(*f_a_generator)
print(f1, a1, sep='\n')
# (10, 25, 50, 75, 100, 1000, 1100)
# (1.0000000000000002, 2.999999999999999, 6.124426027943397, 5.0, 2.999999999999999, 16.193310480660944, 8.010299956639813)
For first array case:
for i in range(1, len(f)-2):
if f[i] == f[i-1]:
del f[i]
print(f)

Possbile optimization fo a Python solution for Timus 1005 - Balanced partition

I have come across the classic problem today.
The problem description is on Timus : 1005.
I know how to solve it in c++.
But when I tried it in python, I got Time Limit Exceeded.
I use brute force but failed. Then I tried DP, also failed.
Here is my solution:
n = int(input())
wi = list(map(int, input().split()))
ans = 1<<21
up = (1<<(n-1))-1
su = 0
for x in range(up, -1, -1):
su = 0
for y in range(n):
su += wi[y] if (x & 1<<y) else -wi[y]
ans = min(ans, abs(su))
print(ans)
It got TLE on Test3.
Here is another DP solution:
n = int(input())
wi = list(map(int, input().split()))
wi.sort()
ans = sum(x for x in wi)
up = ans // 2
dp = [0] * (up + 1)
dp[0] = 1
for x in range(n):
for y in range(up, wi[x]-1, -1):
dp[y] |= dp[y-wi[x]]
aw = up
while not dp[aw]:
aw -= 1
print(ans - 2 * aw)
Got TLE on Test 4.
So my question is how to pass the problem time limit while using Python ?
this just dummy algorithm, and don't know if it returns correct result.
actually for smaller ranges, that I can calculate it always return correct result, but for the greater ones - really don't know :) it should be better to check with your working c++ code, if it's ok.
def minimizing_diff(lst):
a = list()
b = list()
for i in sorted(lst, reverse = True):
if sum(a)>sum(b):
b.append(i)
else:
a.append(i)
return (len(a), a, len(b), b, abs(sum(a)-sum(b)))
# I am returning the first 4 elements to debug by eye :)
These are ok. You can check by pen and papaer :)
0..9 => (5, [9, 6, 5, 2, 1], 5, [8, 7, 4, 3, 0], 1)
0..19 => (10, [19, 16, 15, 12, 11, 8, 7, 4, 3, 0], 10, [18, 17, 14, 13, 10, 9, 6, 5, 2, 1], 0)
0..14 => (7, [14, 11, 10, 7, 6, 3, 2], 8, [13, 12, 9, 8, 5, 4, 1, 0], 1)
Other results (random 20 numbers between 1 and 9999): All of them completed less than 0.1 seconds.
(10, [9944, 8573, 8083, 6900, 6664, 4644, 4544, 2362, 1522, 947], 10, [9425, 8647, 8346, 7144, 6252, 6222, 3749, 1803, 1760, 126], 709)
(10, [9839, 7087, 6747, 6016, 5300, 4174, 3702, 2469, 1970, 1758], 10, [9490, 9246, 6436, 6010, 4690, 4168, 3608, 2374, 1879, 1684], 523)
(10, [9209, 8754, 8613, 6383, 6286, 5222, 4992, 3119, 2950, 147], 10, [9102, 8960, 7588, 7317, 6042, 5769, 4861, 3041, 2078, 1516], 599)
(10, [8096, 7757, 6975, 6677, 5204, 4354, 3174, 3132, 1237, 425], 10, [8033, 7765, 7140, 6089, 5511, 4385, 3482, 2877, 1253, 1139], 643)
(10, [9243, 7887, 6890, 6689, 6347, 5173, 3953, 3380, 3079, 1032], 10, [9131, 7996, 7791, 6403, 5621, 5585, 3632, 3436, 2959, 1291], 172)
(10, [9697, 8504, 7731, 7504, 6696, 4671, 4464, 3057, 1929, 1691], 10, [9384, 8540, 8319, 7233, 6252, 5549, 4275, 2154, 2023, 1794], 421)
Because Python integers can be arbitrarily large, a single integer can be used to represent a boolean array, such as the variable dp in your second solution. This lets you replace the inner loop with a couple of bitwise operations:
ans = sum(wi)
up = ans // 2
mask = 2 ** (up + 1) - 1
dp = 1
for x in wi:
dp |= dp << x & mask
aw = dp.bit_length() - 1
print(ans - 2 * aw)

How to add an item to the top of the array in Python? [duplicate]

This question already has answers here:
Append integer to beginning of list in Python [duplicate]
(10 answers)
Closed 7 years ago.
I have:
[10, 20, 30, 40, 1000, 5000, 0, 5000]
I need:
[-100,10, 20, 30, 40, 1000, 5000, 0, 5000]
How to add an item to the top of the array in Python 3.4?
Try using list.insert(index, element)
listA = [10, 20, 30, 40, 1000, 5000, 0, 5000]
listA.insert(0, -100)
Below code will do..
a = [10, 20, 30, 40, 1000, 5000, 0, 5000]
a.insert(0,-100)
print(a)
Easy, you should use the insert method.
alist=[1,2,3,4,5,6,7]
# to Add
alist.insert(0, 0)
print(alist)
The result will be
#/usr/bin/python3.4 test.py
[0, 1, 2, 3, 4, 5, 6, 7]

Categories