boolean SAT solver recursion - python

I need to make a SAT-solver for a homework assignment. my input gives me n variables x1,x2...xn, and M clauses which are disjuntions of literals , represented by a list of lists ex. [[-1,2,3],[2,-3],[1,-3]] where [-1,3],[2,-3] means (~X1 or X3) and (X2 or ~X3).
My plan is to implement a backtracking solution, where I first choose X1 = False, then recursively check the equation for each other variable.
My problems is how to set up the recursion. I have 2 functions, one which checks for contradictions in my program, and one which simplifies the expression based on the truth value selected for a variable.
def contradiction(clauses):
for clause in clauses:
if clause == ['F']: #checks if any clause evaluates to False
return True
for other in clauses:
if len(clause)==1 and len(other)==1:
if clause[0] == -other[0]: #checks if clauses Xi and ~Xi exist
return True
return False
def simplification(clauses, value):
new_clauses = [i for i in clauses]
for i in range(len(new_clauses)):
if -(value) in new_clauses[i]: #for assignment Xi, removes ~Xi from each clause, + vice versa
del new_clauses[i][new_clauses[i].index(-value)]
if new_clauses[i] == []:
new_clauses[i] = ['F']
if value in new_clauses[i]: #for value Xi, removes clauses satisfied by Xi
new_clauses [i] = []
return new_clauses
I wish to start at X1 and go through recursively down to Xn, stopping the process if a contradiction is found. A non recursive solution is fine as well.
EDIT: this is what I have so far:
def isSat(clauses,variable,n):
if variable > n:
if not contradiction(clauses):
return 'satisfiable'
if contradiction(clauses):
return 'unsatisifable'
clauses1 = simplification(clauses,variable)
clauses2 = simplification(clauses,-variable)
if not contradiction(clauses1):
return isSat(clauses1,variable+1,n)
if not contradiction(clauses2):
return isSat(clauses2,variable+1,n)
return 'unsatisfiable'
essentially I need to, starting from the first variable, set it to true and false, and check both cases for contradictions. Then, for each case that has no contradictions, recursively test both cases for the next variable. It will return satisfiable if it reached the last variable with no contradictions. (It only needs to return yes or no)

Turns out the problem was in a rare edge case where the same variable appeared twice in a clauses I.E. [9,-5,10,3,9]. In this case my simplification function would only remove one instance of the variable. Should not have assumed only 1 instance of a variable per clause

Related

Why does it display "local variable referenced before assignment" even I have assigned it in the same function?

def p3(x,y,ls2):
for i in ls2:
if abs(i[0]-x)==abs(i[1]-y):
c=0
break
else:
c=1
if c==0:
return False
else:
return True
Even I have assigned c in the same function, it still displays "local variable 'c' referenced before assignment"
As the comment by Suraj S says, the problem is if ls2 is an empty list (iterable). Even if that were not a problem, c is a local variable inside the for loop and even though Python allows accessing it outside its scope, it is not a good practice to do so. So it is correct to default initialize it first before running the loop. You can default it to 0 or 1 depending on your use case.
Also, practice Boolean Zen. Don't make redundant checks with booleans. It is better to use True and False instead of 0 and 1.
def p3(x,y,ls2):
c = True # default
for i in ls2:
if abs(i[0]-x)==abs(i[1]-y):
c = False
break
# no need for else at all
return c
Even better:
def p3(x,y,ls2):
for i in ls2:
if abs(i[0]-x)==abs(i[1]-y):
return False
# no need for else at all
return True
If I understand correctly your function, you could simplify it to the following:
def p3(x, y, ls2):
# if no list or list is empty
if not ls2: # the condition could be more convoluted if needed
return 'invalid input' # or return boolean depending on use case
for i in ls2:
# if condition is matched return immediately
if abs(i[0]-x)==abs(i[1]-y):
return False
# the condition was not matched in loop, return default True
return True
You could even simplify more using all that will stop prematurely if the condition is met:
def p3(x, y, ls2):
# if no list or list is empty
if not ls2: # the condition could be more convoluted if needed
return 'invalid input' # or return boolean depending on use case
return all(abs(i[0]-x)!=abs(i[1]-y) for i in ls2)
# or: return not any(abs(i[0]-x)==abs(i[1]-y) for i in ls2)
In general, if you instantiate a variable (namely c) inside of a for loop, it's best not to use that variable outside of that for loop.
The practical reason for this is that you can't be sure that for loop will run and so the variable might not get instantiated. The more high-level reason is to maintain a sense of scope in your code, or "what happens in the for loop, stays in the for loop", if you will.
So in your case, I would recommend instantiating c with a 'default' value (depending on what your logic is) before the for loop starts, so that even if the for loop runs 0 times, c still has a value.
Alternatively, this entire function could be implemented as
def p3(x, y, ls2):
return not any([abs(i[0]-x)==abs(i[1]-y) for i in ls2])

Python - How to find which condition is true in if statement?

I have an if statement has many conditions, for example:
if(0 > 1 or 9 < 10 or 2 == 1):
print('Hello World!')
so i wanna know which is the right condition that let the if statement continues to print hello world? "without using another if statement or elif"
In my code I have lots of conditions so it's difficult to use a lot of else statements to just know what is the right condition was.
In general, it's impossible - each condition is evaluated, we can only get a result.
However, if instead of ors, we have them stored* in a list like this:
conditions = [0>1, 9<10, 2==1] # it gets evaluated here!*
if any(conditions):
print('Hello World!')
we could get the index of the True element by doing conditions.index(True).
[*] But be aware that conditions doesn't consist of pure conditions but of Trues and Falses because they got evaluated. That's why I said it's impossible to get the condition itself.
I don't know why you would ever want to use this but okay...
You need to return a value which has a special __bool__ so I would define a class.
The class will have one instance variable, index to indecate the first True condition, or None if there's no True condition.
The __bool__ function then only needs to check whether index is None:
class Any:
def __init__(self, *conditions):
self.index = None
for i, cond in enumerate(conditions):
if cond:
self.index = i
break
def __bool__(self):
return self.index is not None
Example usage:
if o := Any(0 > 1, 9 < 10, 2 == 1):
print(f'The index of the first True condition is {o.index}')
For hard coded conditions like in your example, a good IDE should have an indicator and propose that you simplify the condition.
If you have variables in the condition, this will of course not be possible. In such a case, I would refactor the code and introduce additional semantics by using a variable name for the individual boolean parts of the condition.
In PyCharm, the shortcut Ctrl+Alt+V extracts a condition into a variable.
A more realistic example (before):
class Customer:
def __init__(self, age, totalsales, paymenttype):
self.age = age
self.totalsales = totalsales
self.paymenttype = paymenttype
c = Customer(21, 3000, 2)
if c.age > 18 or c.totalsales > 5000 or c.paymenttype == 1:
print('Hello World!')
After:
c = Customer(21, 3000, 2)
is_adult = c.age > 18
is_vip = c.totalsales > 5000
is_payed_in_advance = c.paymenttype == 1
if is_adult or is_vip or is_payed_in_advance:
print('Hello World!')
When you reach the if-statement, you can inspect each part of the condition in the debugger.
Note that this may change the behavior of your program, because with the changed code, each condition is evaluated, whereas short circuiting may have been applied before. However, I never ran into a situation where this caused a problem.
Chained boolean expressions will be evaluated from left to right. If one of the chained statements is evaluated as being True, the remaining conditions will not be checked.
Assuming second_condition is fulfilled and hence will be evaluated as True, the following pseudo-code snipped would evaluate first_condition as False and then enter the if statement because of second_condition being True. However, third_condition will not be checked as another check before was already evaluated as True and thus the complete statement will become True:
if (first_condition or second_condition or third_condition):
pass
Knowing which condition was evaluated as True is not possible with the approach shown above. Therefore, I would suggest rewriting your checks as follows:
def handle_true(condition):
pass
if first_condition:
handle_true('first')
elif second_condition:
handle_true('second')
elif third_condition:
handle_true('third')
else:
pass
The if/elif will be evaluated in the same way as your chained or expression. If one condition fails, the next will be checked. If one of the given conditions is evaluated as True the branch will be entered. If none of the given conditions is fulfilled, the default else will be entered.
Combining this with the small helper function handle_true() should do the trick and not only provide a way to check which condition fired, but also provide a single place for handling any True condition.
I think that a good option will be to create a list of condition and to check you the item of your list in a loop.
cond=[True, False, True, 5>10,True,False,1==1,3<-1,'a' == 'a'] # assume this is your list
for i in range(len(cond)):
if cond[i]:
print(i) # will return the Item adress correspending to True
You can do:
print(0 > 1) print(9 < 10)
It will print true or false

Purpose of assigning None to variables

I was looking at the answers for a practice exam and this was a suggested answer for one of the questions:
def multipal(word,n=None):
for i in range(len(word)):
if word[:i] == word[i-1::-1]:
repeat = word[:i]
if not n:
no_repeats = len(word)/i
else:
no_repeats = n
if word == repeat * no_repeats:
return True
return False
The purpose of this function is to determine if the input word is a palindrome (a word that is read the same from the front as it is from the back e.g. 'aza' or 'abba') of 'n' repeats. For example, multipal('abaaba', 2) will return True but multipal('abaaba', 3) will return False.
What I want to know is why n is assigned None in the original argument and does it actually affect the rest of the function?
Assigning something to a function parameter makes it an optional parameter. So you can leave it out when calling the function. In your case, you could call multipal('babbab') and n would get the default value None (because that’s how it was set up in the function definition).
As for how this affects your function, just look at where n is used:
if not n:
no_repeats = len(word)/i
else:
no_repeats = n
So if n has a falsish value (e.g. None, or 0), then the first case will be used which assigns a dynamical repeat count.

Function doesn't stop after "return(False)"

I am trying to write a function which is supposed to compare list structures (the values are indifferent). The problem is that I have two lists which are unequal but the function still returns True even though it actually goes into the else part. I don't understand why and what I did wrong. Here is my code:
def islist(p): #is p a list
return type(p)==type(list())
def ListeIsomorf(a,b):
if len(a)==len(b):
for i,j in zip(a,b):
if islist(i) and islist(j):
ListeIsomorf(i,j)
elif islist(i) or islist(j):
return(False)
return(True)
else:
print(a,"length from the list isn't equal",b)
return(False)
#example lists
ListeE = [[],[],[[]]]
ListeD = [[],[],[[]]]
ListeF = [[[],[],[[]]]]
ListeG = [[],[[]],[[]]]
ListeH = [1,[3]]
ListeI = [1,3]
#tests
print(ListeIsomorf(ListeD,ListeE)) # True
print(ListeIsomorf(ListeD,ListeF)) # False
print(ListeIsomorf(ListeD,ListeG)) # False
print(ListeIsomorf(ListeH,ListeI)) # False
So the problem only occurs with the third print(ListeIsomorf(ListeD,ListeG)) # False. It actually goes into the else part and does print the "length from the list isn't equal" but it doesn't stop and it doesn't give out the return(False). Am I missing something?
The problem is that when your function calls itself recursively:
ListeIsomorf(i,j)
it ignores the returned value.
Thus the comparisons that take place at the second level of recursion have no effect on what the top level returns.
Changing the above to:
if not ListeIsomorf(i,j):
return(False)
fixes the problem.

recursive search boolean return function

Write a recursive function search(l,key) that returns a boolean: True if key is in the list l; False if it isn’t. Describe the base case(s) and how the smaller problem relates to the bigger one. You may not use the in operator or the index() list method.
Can anyone explain what i need to do here for the description? I dont know anything about recurrsion to know where to start. Its for a exam review lab assignment.
Here is the code i have been provided.
def search(l,key):
"""
locates key in list l. if present, returns True; else returns False.
PRE: l is a list.
POST: l is unchanged; returns True if key in l; False otherwise.
"""
Sample Main:
l1 = [1, '2', 'x', 5, -2]
print search(l1, 'x') # should output: "True"
print search(l1, 2) # should output: "False"
All recursion tends to follow the same rules:
Have one or more terminating cases.
Every other case is a slightly simpler form of the current case.
So, for example, a (very inefficient) way to add two positive numbers a and b:
if b is zero, return a.
otherwise add the two numbers a+1 and b-1.
Something like:
def addtwo (a, b):
if b == 0:
return a
return addtwo (a+1, b-1)
Now let's think about the cases for your assignment:
if the list is empty, you didn't find it: return false.
if the first element of the list is equal to your key, you found it: return true.
otherwise look in the list made by removing the first element.
In pseudo-code (which is very similar to Python but different enough that you'll have to do some work):
def search (list, key):
if list is empty:
return false
if key == first item in list:
return true
return search (list with first element removed, key)
Every question regarding recursion should be dealt with the same way (usually)...Ask yourself what is the base case and then build on that for higher cases...
So in this question, ask yourself, when can i be certain there is no key in the list...
It's obviously when the list is empty, you're certain it's not present.
For a bigger list, you compare the first element, if it's the same as the key, you return True right away, but incase it's not, you perform all the checks for the rest of the list....
So studying all these aspects,
Here's your Algorithm.
function locate(lst,key)
if lst == emptylist then return False
if lst[0] == key then return True
else return locate(lst[1..],key) //i use the notation lst[1...] to indicate list starting from 1 index.

Categories