Dynamic Programming puzzle: Find longest consecutive pairs of 3 (different) pieces - python

Puzzle: Xsquare And Chocolates Bars
My approach:
3 pieces form 1 set.
Longest consecutive sets needs to be found out such that all pieces in a set are not same.
Then subtract this from total length of chocolate bar.
Start from index 1 (0 based).
Divide
Case 1: If pieces are same, don't consider current index.
Find from next index.
if bar[current - 1] == bar[current] and bar[current] == bar[current + 1]:
if dp[current + 1] == -1:
dp[current + 1] = CountConsecutiveSets(current + 1)
dp[current] = dp[current + 1]
return dp[current]
Case 2: If pieces are different
Case 2.1: Consider current set: Count 1 for the current set & find remaining sets count after the current set.
if dp[current + 3] == -1:
dp[current + 3] = CountConsecutiveSets(current + 3)
withCurrent = 1 + dp[current + 3]
Case 2.2: Ignore current set: Find remaining sets from the next current
if dp[current + 1] == -1:
dp[current + 1] = CountConsecutiveSets(current + 1)
withoutCurrent = dp[current + 1]
Because max consecutive set has to be found, find max(Case 2.1 & Case 2.2).
Base Case:
When current is at the last index (or greater than last index), set cannot be formed.
So return 0.
Full Code
def CountRemainingCandies(self, bar):
dp = [-1] * (len(bar) + 2)
def CountConsecutiveSets(current):
# Solve small sub-problems
if current >= len(bar) - 1:
dp[current] = 0
return 0
# Divide
# Case 1: If same candies
if bar[current - 1] == bar[current] and bar[current] == bar[current + 1]:
if dp[current + 1] == -1:
dp[current + 1] = CountConsecutiveSets(current + 1)
dp[current] = dp[current + 1]
return dp[current]
# Case 2: If different candies
# Case 2.1: Consider current
if dp[current + 3] == -1:
dp[current + 3] = CountConsecutiveSets(current + 3)
withCurrent = 1 + dp[current + 3]
# Case 2.2: Ignore current
if dp[current + 1] == -1:
dp[current + 1] = CountConsecutiveSets(current + 1)
withoutCurrent = dp[current + 1]
# Combine
dp[current] = max(withCurrent, withoutCurrent)
return dp[current]
consecutiveSetsCount = CountConsecutiveSets(1)
return len(bar) - 3 * consecutiveSetsCount
Test case:
bar = "CCCSCCSSSCSCCSCSSCSCCCSSCCSCCCSCCSSSCCSCCCSCSCCCSSSCCSSSSCSCCCSCSSCSSSCSSSCSCCCSCSCSCSSSCS"
Ans: 39
But above code gives 6.
What's wrong in my thinking & how to fix it?
גלעד ברקן's Top-Down logic:
def CountRemainingCandies(self, bar):
dp = [-1] * len(bar)
def CountConsecutiveSets(current):
# Solve small sub-problems
if current <= 1:
dp[0] = dp[1] = 0
return 0
# Divide
# Case 1: Consider current
# If different candies
withCurrent = -1
if bar[current] != bar[current - 1] or bar[current] != bar[current - 2]:
if current - 3 < 0: current = 0
if dp[current - 3] == -1:
dp[current - 3] = CountConsecutiveSets(current - 3)
withCurrent = 1 + dp[current - 3]
# Case 2: Ignore current
if current - 1 < 0: current = 0
if dp[current - 1] == -1:
dp[current - 1] = CountConsecutiveSets(current - 1)
withoutCurrent = dp[current - 1]
# Combine
dp[current] = max(withCurrent, withoutCurrent)
return dp[current]
consecutiveSetsCount = CountConsecutiveSets(len(bar) - 1)
return len(bar) - 3 * consecutiveSetsCount
I'm using גלעד ברקן's logic but still getting wrong answer for test case
bar = "CCCSCCSSSCSCCSCSSCSCCCSSCCSCCCSCCSSSCCSCCCSCSCCCSSSCCSSSSCSCCCSCSSCSSSCSSSCSCCCSCSCSCSSSCS"

Here's working top-down in Python (f(str, i) returns a tuple, (overall_best, sequence_length_up_to_i):
# Returns the sequence length,
# not the problem answer, which
# is the length of the chocolate
# after removing the sequence.
def f(s, i, memo):
if i in memo:
return memo[i]
if i < 2:
return (0, 0)
(best, _) = f(s, i-1, memo)
if s[i] != s[i-1] or s[i] != s[i-2]:
seq_len = 3 + f(s, i - 3, memo)[1]
else:
seq_len = 0
memo[i] = (max(best, seq_len), seq_len)
return memo[i]
s = "CCCSCCSSSCSCCSCSSCSCCCSSCCSCCCSCCSSSCCSCCCSCSCCCSSSCCSSSSCSCCCSCSSCSSSCSSSCSCCCSCSCSCSSSCS"
print(f(s, len(s) - 1, {})[0]) # 51

Since the sequence has to be built of exact blocks of three, we can consider each square just as the third in the last block of the sequence. Note that we can also reduce space to O(1) by updating just four cells.
JavaScript code:
// Returns the sequence length,
// not the problem answer, which
// is the length of the chocolate
// after removing the sequence.
function f(s){
if (s.length < 3)
return 0
let dp = new Array(s.length+1).fill(0)
let best = 0
for (let i=2; i<s.length; i++){
if (s[i] != s[i-1] || s[i] != s[i-2])
dp[i+1] = 3 + dp[i-2]
best = Math.max(best, dp[i+1])
}
return best
}
var strs = [
"SSSSSSSSS",
"CCCCCCCCC",
"SSSSCSCCC",
"SSCCSSSCS",
"CCCSCCSSSCSCCSCSSCSCCCSSCCSCCCSCCSSSCCSCCCSCSCCCSSSCCSSSSCSCCCSCSSCSSSCSSSCSCCCSCSCSCSSSCS"
]
for (let s of strs)
console.log(f(s))

Related

Number of subsets with a given sum . Recursion not tracing few branches of the tree

I have written this code in python and it is not yielding the right answer for the input wt[]=[2,3,5,6,8,10] in this order . It is giving right answer for few other combinations like wt[]=[3,2,6,10,8,5].I I have also tried tracing the recursive tree to debug accordingly but still cannot figure out why the code is not tracing some branches of the tree.
Kindly please help me figure out the problem.
Thank you!
n=6 #int(input())
m=10 #int(input())
wt=[2,3,5,6,8,10]
dp_table=[[-1 for i in range(n+1)]for j in range (m+1)]
total=[0]
def SS(m,n):
a=0
b=0
if m==0:
print(n-1)
total[0]=total[0]+1
return 0;
if n==0:
return 0;
else:
if wt[n-1]>m:
return (SS(m,n-1));
else:
if dp_table[m-wt[n-1]][n-1]==-1:
a=SS(m-wt[n-1],n-1) + wt[n-1]
if dp_table[m][n-1]==-1:
b=SS(m,n-1)
dp_table[m][n]=max(a,b)
return dp_table[m][n];
if m==0 or n==0:
print("Not Possible!")
if SS(m,n)==m:
print("Possible and the no of subsets with equal sum are: ",total[0])
else:
print("False")
You're storing results in dp_table but never using them (giving incorrect results). If the dp_table value of an entry isn't -1, you're ignoring the result (and assuming it's always 0).
Often it's better to do the cache-checks at the top of the function (or better yet, use functools.cache).
If you really want to keep the code structured as it is now, this will fix the issue:
def SS(m, n):
a = dp_table[m - wt[n - 1]][n - 1]
b = dp_table[m][n - 1]
if m == 0:
total[0] += 1
return 0
if n == 0:
return 0
else:
if wt[n - 1] > m:
dp_table[m][n] = b if b != -1 else SS(m, n - 1)
else:
if a == -1:
a = SS(m - wt[n - 1], n - 1)
a += wt[n - 1]
if b == -1:
b = SS(m, n - 1)
dp_table[m][n] = max(a, b)
return dp_table[m][n]
If you want to do the caching yourself, you can put cache checks at the top (a better approach), rather than putting a check (and an if-else statement) before every recursive call, like this:
def SS2(m, n):
if dp_table[m][n] != -1:
return dp_table[m][n]
if m == 0:
total[0] += 1
dp_table[m][n] = 0
elif n == 0:
dp_table[m][n] = 0
else:
if wt[n - 1] > m:
dp_table[m][n] = SS(m, n - 1)
else:
dp_table[m][n] = max(SS(m - wt[n - 1], n - 1) + wt[n - 1], SS(m, n - 1))
return dp_table[m][n]
But the most 'Pythonic' and least work alternative is to use the decorators built into the standard library. You can limit the total memory usage, and it might even be faster if your manual DP-table happens to have cache-unfriendly access patterns.
import functools
#functools.cache
def SS3(m, n):
if m == 0:
total[0] += 1
return 0
elif n == 0:
return 0
else:
if wt[n - 1] > m:
return SS(m, n - 1)
else:
return max(SS(m - wt[n - 1], n - 1) + wt[n - 1], SS(m, n - 1))

Python Knapsack Problem with fixed number of elements

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

How to fix "list index out of range" error for a python code that returns the max increase within a list?

So I've written up this code to get me the maximum increase from a smaller element to a larger one of a higher index, with the trend in between only increasing, but I hit a "list" and "tuple index out of range" error at "while seq[index + 1] > seq[index]"
so for successful examples:
max_increase((1,2,3,2)) == 2.0
and
max_increase([3,-1,2,1]) == 3.0
this is my code below:
def max_increase(seq):
index = 1
inc = 0
diff = 0
n = 1
for index in range(1, len(seq)):
if seq[index] > seq[index - 1]:
diff = seq[index] - seq[index - 1]
while seq[index + 1] > seq[index]:
diff += seq[index + n] - seq[index]
index = index + 1
if diff > inc:
inc = diff
index = index + 1
else:
index = index + 1
return inc
Not really any minimum code but basic code is preferred as this is an introductory coding course :)
Tests that got me the error are:
max_increase([1.0,3.0,1.0,2.0]) == 2.0
and
btc_data = [ 6729.44, 6690.88, 6526.36, 6359.98, 6475.89, 6258.74,
6485.10, 6396.64, 6579.00, 6313.51, 6270.20, 6195.01,
6253.67, 6313.90, 6233.10, 6139.99, 6546.45, 6282.50,
6718.22, 6941.20, 7030.01, 7017.61, 7414.08, 7533.92,
7603.99, 7725.43, 8170.01, 8216.74, 8235.70, 8188.00,
7939.00, 8174.06 ]
btc_data.reverse()
assert abs(max_increase(tuple(btc_data))-589.45) < 1e-6
you put extra condition like this, looks like you have programmatic error
def max_increase(seq):
index = 1
inc = 0
diff = 0
n = 1
for index in range(1, len(seq)):
if seq[index] > seq[index - 1]:
diff = seq[index] - seq[index - 1]
while index != (len(seq)-1) and seq[index + 1] > seq[index]:
diff += seq[index + n] - seq[index]
index = index + 1
if diff > inc:
inc = diff
index = index + 1
else:
index = index + 1
return inc
max_increase([1.0,3.0,1.0,2.0]) == 2.0

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.

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

Categories