Subset Sum using Sets (Python) - python

I'm trying to solve the Subset Sum problem using Set and recursion in python.
I've found a lot of solutions with an array, but none with a set.
Here is my code:
def checkForSubSet(set, sum):
if len(set) == 0 or sum < 0:
return False
e = set.pop()
if sum == e:
return True
return checkForSubSet(set, sum - e) or checkForSubSet(set, sum)
if __name__ == '__main__':
set = {3, 34, 4, 12, 5, 2}
sum = 17
if (checkForSubSet(set, sum) == True) :
print("Found a subset with given sum")
else :
print("No subset with given sum")
sometimes its works and sometimes not... any idea?
ty for any help!

Your basic error is that you pop() a value from the input set. This means that in all subsequent calls you will not have that value available. This is easily seen with a print statement, where I've replaced sum and set with names that don't clash with builtins:
def checkForSubSet(components, target):
print(f"{components=} {target=}")
if len(components) == 0 or target< 0:
return False
e = components.pop()
if target == e:
return True
return checkForSubSet(components, target - e) or checkForSubSet(components, target)
>>> checkForSubSet({3,2,1}, 4)
components={1, 2, 3} target=4
components={2, 3} target=3
components={3} target=1
components=set() target=-2
components=set() target=1
components=set() target=3
components=set() target=4
False
We see that the set gets ordered. 1 is tested first, and then popped off. And so on until 3 is popped off and then, hitting the base case, the function returns False. It tries the alternative cases - but the set (which persists throughout) no longer has items in it. Thus, as it goes back up the tree it is testing against an empty set.
Recursion is always about a base case and an iterative case:
def has_subset(components: Set[int], target: int) -> bool:
print(f"{components=} {target=}")
# Base Case: check for truth once the set is empty
if 0 == target:
return True
# Iterative Case: iterate through each option you have for reducing the size of the set by one
else:
return any(has_subset(components - {item}, target - item) for item in components)
Here, we check the base case of an empty set. If we have an empty set and the target is 0, we return true. Otherwise, we return false.
The iterative case is (almost always) more interesting. Here, we simply iterate through all elements of the set, and check what has_subset() returns if we remove one item and reduce our target by that item. any() lets us return the first result we get that is True, or False if we exhaust all options. Note, however, that we are using the construct components - {item} which takes your initial set and returns a new set that includes all the elements except those in the set {item}. Since this is a new set, it does not fall prey to the previous problem when we recurse with it.
There are optimizations that can happen (what happens if your choice makes target a negative number?), but this should still work.

Related

Error: variable not storing modified value on recursion

I am finding count of all the ways a target is reached. In base case, i am updating the value but when returning, it is taking the initial value only. How to change it to updated value, Kindly help me making changes in this code only and let me know how can i store so that it can return the modified value.
Input list:[1,2,3]
target:3
Output: 2 as [1,2] and [3] will make it 3
def counter(ind,grid,target,count):
if target==0: #if target becomes 0(achieved)
count+=1
return count
if ind==0: #if ind=0 is reached and target=value at that index(achieved)
if target==grid[ind]:
count+=1
return count
else:
return
nottake=counter(ind-1,grid,target,count) #not taking the index's value
take=0
if target-grid[ind]>=0: #only if value at index is smaller that target
take=counter(ind-1,grid,target-grid[ind],count) #taking the index's value
return count
grid=[1,2,3]
target=3
ind=len(grid)-1
print(counter(ind,grid,target,0)) #output should be 2 but i am getting 0
For starters, please format your code with Black. It's difficult to understand code that's scrunched together. Also, use default values to avoid burdening the caller with fussy indices and extra parameters.
This approach exhibits a common mistake with recursion: trying to pass the result downward through the recursive calls as well as upward. Just pass the result upward only, and pass parameters/state downward.
Doing so gives:
from functools import cache
#cache
def count_ways(available_numbers, target, i=0):
if target == 0:
return 1
elif target < 0:
return 0
elif i >= len(available_numbers):
return 0
take = count_ways(available_numbers, target - available_numbers[i], i + 1)
dont_take = count_ways(available_numbers, target, i + 1)
return take + dont_take
if __name__ == "__main__":
print(count_ways(available_numbers=(1, 2, 2, 1, 3, 4) * 70, target=7))
This is clearly exponential since each recursive call spawns 2 child calls. But adding a cache (formerly lru_cache(maxsize=None) prior to CPython 3.9) avoids repeated calls, giving a linear time complexity as long as the list fits within the stack size. Use a bottom-up dynamic programming approach if it doesn't

Finding the index of a list

My function gives the error ("IndexError: list index out of range"), and I'm not to sure why, even first putting i = 0. My code is to print the index of the first element that is equal to the target value and if it doesn't equal anything in the list then index = -1. (Using While loop)
Function
def yareyare(list_g, list_size, target):
found = False
i = 0
while i < list_size or not found:
if target == list_g[i]:
found = True
result = i
else:
i += 1
if not found:
result = -1
print("The index is {}".format(result))
Main
# Imports
from index import yareyare
# Inputs
list_g = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
list_size = len(list_g)
target = str(input("What is the target value(1-10): "))
#
yareyare(list_g, list_size, target)
There are two simple errors in the code.
First, the boolean logic of the while loop should be and not or because it allows it to loop forever since found doesn't become True until the target is found.
Second, you need to convert target to an int not an str
It should work after that.
while i < list_size or not found: is the culprit. As long as found is false, the loop continues even if it ran out of list entries. That said, the entire code seems very clumsy; a Python approach would normally use list.index, or for more verbosity, a for loop with enumerate and an else clause.
There are several problems with this code. I am going to try and fix your errors while changing as little of your original code as possible. There are some things that you are doing that will work that I would advise against, like passing the list length as a parameter rather than using len and using a while loop for this at all.
Your first issue is that target is a string while the goals you compare it to are ints. That means this:
target == list_g[i]
Will never be true. It must be changed to:
int(target) == list_g[i]
Your second problem is that you are looping while i < list_size OR found is false. When you find a match, you set found to false but you never increment i. This means that i will always stay at the same value and therefore it will always equal list_g[i] so you will never increment it. Since i is always less than the list length i < list_size or not found will always be true and you will be trapped in an infinite loop. You should change the or to an and.
Here is your function with the fixes:
def yareyare(list_g, list_size, target):
found = False
i = 0
while i < list_size and not found:
if int(target) == list_g[i]:
found = True
result = i
else:
i += 1
if not found:
result = -1
print("The index is {}".format(result))

Recursively running a for loop within a for loop (python)

I'm new to python and had the idea of trying to use it to solve the numbers game on the TV show countdown. (rules for those unfamiliar). I googled and it turns out this has been done before - But I didn't understand the code properly and then thought why not just give it a go myself. I have searched and there are other people looking for recursive solutions but I couldn't get them to work for my example (Apologies, I am very new to this after all).
What I'm trying to do is to take a list of numbers, then loop through applying operations to pairs of them and replacing that pair with the output. This would repeat recursively until either we find the number we are looking for, or the list of numbers is reduced to size 1.
My function "single_move_generator" is a generator producing tuples of the form ((a,b,operation), answer, numbers left to use). I want to feed this final part of the tuple back into the function as the new list, but also keep track of the first part as it is the 'history' of how we achieved our answer. At the moment I have the following:
target = 155
numbers_to_use = [6, 25, 3, 2]
for a in single_move_generator(numbers):
if a[1] == target:
print(a[0])
for b in single_move_generator(a[2]):
if b[1] == target:
print(a[0],b[0])
quit()
for c in single_move_generator(b[2]):
if c[1] == target:
print(a[0],b[0],c[0])
quit()
produces:
(25, 6, 'add') (3, 2, 'add') (31, 5, 'multiply')
But I'd like to able to give it a larger list of numbers and have it just continue until the list reaches size one. I suspect I need a while loop - but this attempt doesn't work. It doesn't find the target or keep track of the history of moves.
numbers_available = numbers
while len(numbers_available) >1 and target not in numbers_available:
for a in single_move_generator(numbers_available):
if a[1] == target:
print("Target Found", a)
break
numbers_available = a[2]
numbers_available = a[2]
I feel like there must be a pythonic way of doing this which is far neater than I've done - any hints would be greatly appreciated. Thanks!
Based on your idea of using tuples (i, j, operation), I wrote the following. This is a recursive solution, as the main function calls itself back.
from itertools import combinations, product
def check_operation(i, j, operation):
"""
Check whether 'i operation j' is permitted.
"""
if operation == '/' and j == 0:
return False
elif operation == '/' and i%j != 0:
return False
# if not playing with negative values
#elif operation == '-' and i-j < 0:
# return False
else:
return True
def countdown(target, numbers, trackback):
if target in numbers:
print trackback
for possibility in product(combinations(numbers,2), ['+', '*', '/', '-']):
new_numbers = [k for k in numbers] # copy list, use list.copy in python 3
i, j = possibility[0][0], possibility[0][1]
operation = possibility[1]
new_numbers.remove(i)
new_numbers.remove(j)
if not check_operation(i, j, operation):
continue
new_numbers.append(eval('%i%s%i' % (i, operation, j)))
countdown(target, new_numbers, trackback+[possibility])
countdown(155, [6, 25, 3, 2], [])
It only works if a solution exists, since it won't intend to get as close to the solution as possible. However it will return all the solutions, not only one.
Based on what you posted, this should work for you:
def solve(numbers, target):
for op, res, nums in single_move_generator(numbers):
if res == target:
return [op]
ops = solve(nums, target)
if ops:
return [op] + ops
print(*solve(numbers_to_use, target))
It should be equivalent to the nested for loop you posted.
Bottom of the recursion is hit when res == target. Python functions return None by default, so if a recursive call to solve returned a truthy value then it must have hit the target. ops will contain the last operation if it was the bottom of the recursion. It is then appended to the operation that launched the recursive call and returned to an upper level. So the function will return all operations at the top level.

checking an array for 5 consecutive numbers

I was making a poker simulator and tried to define a function which would identify a straight and give it a handstrength value of 5.
def straightCheck(playerHand):
playerHand.sort()
print(playerHand)
for playerHand in range(len(playerHand)):
for i in playerHand:
if playerHand[i] == playerHand [i+1] -1:
straight = True
else:
straight = False
if straight == True:
handstrength = 5
x = [1,3,5,4,2]
straightCheck(x)
i can't figure out what is wrong with is but it keeps returning this error message:
for i in playerHand:
TypeError: 'int' object is not iterable
First of all you are trying to iterate of an integer, which cannot (and shouldn't) be done. It seems like your two nested for loops should just be one for, like for i in range(len(playerHand) - 1), when the -1 is used so that you don't try to accessplayerHand[len(playerHand)].
In addition, since you set straight to True or False in every iteration, only your last iteration will count, so you'll get false positives.
Finally, I'm not sure whether you want your function to return a value, but currently your function returns no data (unless handstrength is a global variable). Also, please note that currently, by using .sort() you are actually sorting playerHand, thus changing it from within the function - this might not be what you want.
A possible function to check whether a hand is straight, similar to your code, is this:
def is_straight(playerHand):
playerHand.sort()
for i in range(len(playerHand) - 1):
if playerHand[i] != playerHand [i+1] - 1:
return False
return True
This function returns True if playerHand is straight, and False otherwise.
What about this. You sort the list, then convert it to a set and back to a list, which makes it unique. Then the length must be 5 and if so, the difference between max and min for 5 consecutive numbers must be 4. I can't mathematically prove this here, but it should be. ;)
>>> x=[2,1,5,3,4]
>>> y=sorted(x)
>>> y=list(set(y))
>>> if len(y) == 5 and y[4]-y[0] == 4:
... print "straight"
...
straight
Also see this here: Check for consecutive numbers

How to determine if a two dimensional list is "rectangular"

Right now I'm trying to get my program to take a two dimensional list as an input and return either true or false depending on whether or not it is "rectangular" (eg. [[2,3],[1,5],[6,9]] is rectangular whereas [[2,3],[1,8,6]] is not.) So far I've come up with this:
def rectangular(List):
n = List
for i in n:
if len(i) != len(n[0]):
return False
elif len(i) == len(n[0]):
i
I can't seem to figure out how to create a "true" case. Using the elif above I'm able to cycle through the list but if i were to add a return true portion it stops as soon as that is the case. Would a while loop work better in this case? All help is appreciated! Thanks.
If you get to the end without finding a false case, then you know it's true, right? There aren't any other possibilities.
So, you can just remove the elif entirely, and just add a return True at the end:
def rectangular(List):
n = List
for i in n:
if len(i) != len(n[0]):
return False
return True
As a side note, your elif that has exactly the opposite condition as the if is better written as just else:. That way, there's no chance of you getting the opposite condition wrong, no need for your readers to figure out that it's the opposite, etc.
Also, there's no reason to take the argument as List, then bind the same value to n and use that. Why not just take n in the first place?
def rectangular(n):
for i in n:
if len(i) != len(n[0]):
return False
return True
You can make this more concise, and maybe more Pythonic, by replacing the for statement with a generator expression and the all function:
def rectangular(n):
return all(len(i) == len(n[0]) for i in n)
But really, this isn't much different from what you already have. You should learn how this works, but if you don't understand it yet, there's no problem doing it the more verbose way.
If you want to get clever:
def rectangular(n):
lengths = {len(i) for i in n}
return len(lengths) == 1
We're making a set of all of the lengths. Sets don't have duplicates, so this is a set of all of the distinct lengths. If there's only 1 distinct length, that means all of the lengths are the same.
However, note that for an empty list, this will return False (because there are 0 lengths, not 1), while the other two will return True (because a condition is vacuously true for all values if there are no values to test). I'm not sure which one you want, but it should be relatively easy to figure out how to change whichever one you choose to do the opposite.
Try using the all function with a generator:
def rectangular(lst):
first_len = len(lst[0])
# I used lst[1:] to skip the 0th element
return all(len(x) == first_len for x in lst[1:])
The all function returns True if all elements of an iterable are True, and False otherwise.
It's good that you didn't call your variable list, but capitalized names usually represent a class in Python, so lst is a better choice than List.
NOTE: I made the assumption that "rectangular" means each sublist is the same length. If in reality each sublist should be (say) 2 elements long, just replace first_len with the literal 2 and remove the [1:] on lst[1:]. You may also want to add some exception handling in case you pass a list with only one element.
You can make sure that the lengths of all elements of the list are the same length. Or in Python:
all(map(lambda m: len(m) == len(x[0]), x))
Where x is what you want to check.
The only problem with this solution is that the if the list looks like [ [1,2], [1,[1,2]], 'ab' ], it is still going to return True. So you additionally need to do some type checking.

Categories

Resources