3-SAT Solver Python
Ich habe einen 3-SAT- Solver basierend auf dieser Eingabeaufforderung geschrieben:
Alice hat vor kurzem angefangen, für ein Hardware-Design-Unternehmen zu arbeiten. Als Teil ihrer Arbeit muss sie Fehler in hergestellten integrierten Schaltkreisen identifizieren. Ein Ansatz zum Identifizieren dieser Defekte läuft darauf hinaus, eine Erfüllbarkeitsinstanz zu lösen. Sie braucht Ihre Hilfe, um ein Programm für diese Aufgabe zu schreiben.
Eingabe
Die erste Eingabezeile enthält eine einzelne Ganzzahl, nicht mehr als 5, die die Anzahl der folgenden Testfälle angibt. Die erste Zeile jedes Testfalls enthält zwei ganze Zahlen n und m, wobei 1 ≤ n ≤ 20 die Anzahl der Variablen und 1 ≤ m ≤ 100 die Anzahl der Klauseln angibt. Dann folgen m Zeilen entsprechend jeder Klausel. Jede Klausel ist eine Disjunktion von Literalen in der Form Xi oder ~ Xi für einige 1 ≤ i ≤ n, wobei ~ Xi die Negation des Literals Xi angibt. Der Operator "oder" wird durch ein "v" -Zeichen gekennzeichnet und durch ein einzelnes Leerzeichen von Literalen getrennt.Ausgabe
Zeigen Sie für jeden Testfall in einer einzelnen Zeile zufriedenstellend an, wenn eine zufriedenstellende Zuordnung vorliegt. sonst unbefriedigend anzeigen.Probeneingabe
2 3 3 X1 v X2 ~X1 ~X2 v X3 3 5 X1 v X2 v X3 X1 v ~X2 X2 v ~X3 X3 v ~X1 ~X1 v ~X2 v ~X3Beispielausgabe
satisfiable unsatisfiable
Dieser Code enthält im Wesentlichen mySetseine Liste von Mengen, die alle mögliche Kombinationen von Literalen darstellen, die die gesamte Aussage wahr machen könnten. Jedes Mal, wenn wir eine neue Klausel analysieren, prüfen wir, ob ihre Negation bereits in einer Menge vorhanden ist. Wenn dies der Fall ist, ist die Menge nicht enthalten.
Das funktioniert, läuft aber etwas langsam.
import sys
cases = int(sys.stdin.readline())
def GetReverse(literal):
if literal[0] == '~':
return literal[1:]
else:
return '~' + literal
for i in range(cases):
vars, clauses = map(int, sys.stdin.readline().split())
mySets = []
firstClause = sys.stdin.readline().strip().split(" v ")
for c in firstClause:
this = set()
this.add(c)
mySets.append(this)
for i in range(clauses-1):
tempSets = []
currentClause = sys.stdin.readline().strip().split(" v ")
for s in mySets:
for literal in currentClause:
if not s.__contains__(GetReverse(literal)):
newset = s.copy()
newset.add(literal)
tempSets.append(newset)
mySets = tempSets
if mySets:
print("satisfiable")
else:
print("unsatisfiable")
Ich denke, das Problem liegt hier an den eingerückten for-Schleifen. 3-SAT soll exponentiell sein, aber ich möchte es etwas beschleunigen (vielleicht durch Entfernen einer Schleife?)
for i in range(clauses-1):
tempSets = []
currentClause = sys.stdin.readline().strip().split(" v ")
for s in mySets:
for literal in currentClause:
if not s.__contains__(GetReverse(literal)):
newset = s.copy()
newset.add(literal)
tempSets.append(newset)
mySets = tempSets
Antworten
Wenn Sie Ihren Code mit strategisch platzierten Druckanweisungen instrumentieren, werden Sie feststellen, dass einige wiederholte Berechnungen durchgeführt werden. Im zweiten Testfall bei der Verarbeitung der Klausel X2 v ~X3wird die Menge zweimal {'X1', 'X2'}hinzugefügt mySets. Bei der Verarbeitung der Klausel X3 v ~X1wird die Menge dreimal {'X3', 'X1', 'X2'}hinzugefügt mySets.
In großen Fällen kann es schneller sein, Änderungen mySetsan set()einer Liste vorzunehmen, um die Duplikate zu entfernen. Dann müssen die inneren Sätze sein frozensets.
mySetsist eine Reihe möglicher Lösungen, die alle Klauseln erfüllen, daher habe ich sie in umbenannt candidates.
Wenn Sie initialisieren candidates, um eine einzelne leere Menge zu enthalten, muss die erste Klausel nicht separat behandelt werden.
Ich denke, Sie können jederzeit aufhören, wenn candidateses leer ist.
Teilen Sie den Code auch in Funktionen auf.
def is_satisfiable(n_vars, clauses):
candidates = {frozenset()}
for clause in clauses:
temp = set()
for s in candidates:
for literal in clause:
if GetReverse(literal) not in s:
temp.add(s | {literal})
candidates = temp
if len(candidates) == 0:
return False
return True
def load_case(f):
n_vars, n_clauses = f.readline().split()
clauses = [f.readline().strip().split(' v ') for _ in range(int(n_clauses))]
return int(n_vars), clauses
def main(f=sys.stdin):
num_cases = int(f.readline())
for i in range(num_cases):
n_vars, clauses = load_case(f)
result = is_satisfiable(n_vars, clauses)
print(f"{'satisfiable' if result else 'unsatisfiable'}")
Genannt wie:
import io
data = """
2
3 3
X1 v X2
~X1
~X2 v X3
3 5
X1 v X2 v X3
X1 v ~X2
X2 v ~X3
X3 v ~X1
~X1 v ~X2 v ~X3
""".strip()
main(io.StringIO(data))
oder
import sys
main(sys.stdin)
Hier ist eine vorgeschlagene Implementierung, die im Grunde nichts an Ihrem Algorithmus ändert, aber
- hat die richtige Einrückung
- verwendet ein wenig Typhinweis
- verwendet Set-Literale und Generatoren
- wird
_für "nicht verwendete" Variablen verwendet - fügt ein hinzu,
parse_clause()weil der Klauselcode wiederholt wird - verwendet a
StringIOfür diese Zwecke,stdinum die von Ihnen gezeigte Beispieleingabe effektiv zu verspotten und zu verwenden - verwendet PEP8-kompatible Namen (mit Unterstrichen)
from io import StringIO
from typing import List
stdin = StringIO('''2
3 3
X1 v X2
~X1
~X2 v X3
3 5
X1 v X2 v X3
X1 v ~X2
X2 v ~X3
X3 v ~X1
~X1 v ~X2 v ~X3
'''
)
def get_reverse(literal: str) -> str:
if literal[0] == '~':
return literal[1:]
return '~' + literal
def parse_clause() -> List[str]:
return stdin.readline().strip().split(' v ')
n_cases = int(stdin.readline())
for _ in range(n_cases):
n_vars, n_clauses = (int(s) for s in stdin.readline().split())
my_sets = [{c} for c in parse_clause()]
for _ in range(n_clauses - 1):
temp_sets = []
current_clause = parse_clause()
for s in my_sets:
for literal in current_clause:
if get_reverse(literal) not in s:
new_set = s.copy()
new_set.add(literal)
temp_sets.append(new_set)
my_sets = temp_sets
if my_sets:
print('satisfiable')
else:
print('unsatisfiable')