## Lekcja – Pakowanie plecaka – metoda dynamiczna (dynamic programming)

def knapsack_dynamic(total_weight, weights, values):

n = len(values)
tab = [[0 for i in range(total_weight + 1)] for j in range(n + 1)]
tab_lists = [[ [] for i in range(total_weight + 1)] for j in range(n + 1)]

for obj_row in range(n + 1):
for w in range(total_weight + 1):
if obj_row == 0  or  w == 0:
tab[obj_row][w] = 0
tab_lists[obj_row][w] = []

# The total weight after including object [i] should not exceed the weight limit.
elif weights[obj_row-1] <= w: # The profit after including object [i] should be greater as compared to when the object is not included. if values[obj_row-1] + tab[obj_row-1][w-weights[obj_row-1]] > tab[obj_row-1][w]:
tab[obj_row][w] = values[obj_row-1] + tab[obj_row-1][w-weights[obj_row-1]]
tab_lists[obj_row][w] = tab_lists[obj_row-1][w-weights[obj_row-1]].copy()
tab_lists[obj_row][w].append(values[obj_row-1])
else:
tab[obj_row][w] = tab[obj_row-1][w]
tab_lists[obj_row][w] = tab_lists[obj_row-1][w].copy()
else:
tab[obj_row][w] = tab[obj_row-1][w]
tab_lists[obj_row][w] = tab_lists[obj_row-1][w].copy()

return tab[n][total_weight], tab_lists[n][total_weight]

values = [6, 10, 12, 3, 60]
weights = [1, 2, 3, 5, 2]
total_weight = 5
print(knapsack_dynamic(total_weight, weights, values))

values = [60, 100, 120, 30, 600]
weights = [10, 20, 30, 5, 20]
total_weight = 50
print(knapsack_dynamic(total_weight, weights, values))

## Rozwiązanie

def coin_change_dynamic(coins, value):

tab_numbr = [[ 0  for v in range(value + 1)] for c in range(len(coins) + 1)]
tab_coins = [[ [] for v in range(value + 1)] for c in range(len(coins) + 1)]
for i in range(1, value + 1):
tab_numbr[0][i] = float('inf')  # by default change is impossible

# for each row of the constructed table
for coin_row_idx in range(1, len(coins)+1):
current_coin = coins[coin_row_idx - 1]
for value_limited in range(1, value + 1):

# current_coin is too big - we need to take value from the previous row
if current_coin > value_limited:
tab_numbr[coin_row_idx][value_limited] = tab_numbr[coin_row_idx - 1][value_limited]
tab_coins[coin_row_idx][value_limited] = tab_coins[coin_row_idx-1][value_limited].copy()

# yes - we could take this coin
else:
# the value after adding this coin will be worse (higher) than in the previous row
# so copy data from the previous row
if tab_numbr[coin_row_idx - 1][value_limited] <= 1 + tab_numbr[coin_row_idx][value_limited - current_coin]:
tab_numbr[coin_row_idx][value_limited] = tab_numbr[coin_row_idx - 1][value_limited]
tab_coins[coin_row_idx][value_limited] = tab_coins[coin_row_idx - 1][value_limited].copy()
# the value after adding this coin will be better (lower) than in the previus row
# so take this coin
else:
tab_numbr[coin_row_idx][value_limited] =  1 + tab_numbr[coin_row_idx][value_limited - current_coin]
tab_coins[coin_row_idx][value_limited] = tab_coins[coin_row_idx][value_limited - current_coin].copy()
tab_coins[coin_row_idx][value_limited].append(current_coin)

for row in tab_numbr:
print(row)

return tab_numbr[-1][-1], tab_coins[-1][-1]

coins = [1,2,5,10,20]
value = 7
change = coin_change_dynamic(coins, value)
print(value, change)

# coins = [1,2,5,10,20]
# for value in range(30):
#     change = coin_change_dynamic(coins, value)
#     print(value, change)