3-SAT Решатель Python

Oct 22 2020

Я написал решатель 3-SAT на основе этой подсказки:

Алиса недавно начала работать в компании по разработке оборудования, и в рамках ее работы ей необходимо выявлять дефекты в изготовленных интегральных схемах. Подход к выявлению этих дефектов сводится к решению примера выполнимости. Ей нужна ваша помощь, чтобы написать программу для этой задачи.

Входные данные
Первая строка входных данных содержит одно целое число, не более 5, указывающее количество следующих тестовых примеров. Первая строка каждого тестового примера содержит два целых числа n и m, где 1 ≤ n ≤ 20 указывает количество переменных, а 1 ≤ m ≤ 100 указывает количество предложений. Затем следуют m строк, соответствующих каждому предложению. Каждое предложение представляет собой дизъюнкцию литералов в форме Xi или ~ Xi для некоторого 1 ≤ i ≤ n, где ~ Xi указывает отрицание литерала Xi. Оператор «или» обозначается символом «v» и отделяется от литералов одним пробелом.

Выходные данные
Для каждого тестового примера отобразить удовлетворительно в отдельной строке, если есть удовлетворительное задание; в противном случае отображение неудовлетворительное.

Пример ввода

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 

Пример вывода

satisfiable
unsatisfiable

Этот код в основном поддерживает mySetsсписок наборов, каждый из которых представляет возможные комбинации литералов, которые могут сделать все утверждение истинным. Каждый раз, когда мы разбираем новое предложение, мы проверяем, существует ли уже отрицание в наборе, если оно есть, набор не включается.

Это работает, но работает немного медленно.

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")

Я думаю, что проблема здесь из-за отступов для циклов. Предполагается, что 3-SAT будет экспоненциальным, но я хотел бы немного его ускорить (возможно, удалив цикл?)

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

Ответы

2 RootTwo Oct 23 2020 at 04:47

Если вы инструментируете свой код с помощью некоторых стратегически размещенных операторов печати, вы увидите, что происходят некоторые повторяющиеся вычисления. Во втором тестовом примере при обработке предложения X2 v ~X3набор {'X1', 'X2'}добавляется mySetsдважды. При обработке предложения X3 v ~X1набор {'X3', 'X1', 'X2'}добавляется до mySetsтрех раз.

В больших случаях это может ускорить переход mySetsна a set()вместо списка для устранения дубликатов. Тогда внутренние наборы должны быть frozensets.

mySets- это набор возможных решений, удовлетворяющих всем пунктам, поэтому я переименовал его в candidates.

Если вы инициализируете candidatesодин пустой набор, то первое предложение не нужно обрабатывать отдельно.

Думаю, можно остановиться в любое время candidatesпусто.

Кроме того, разделите код на функции.

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'}")

Называется как:

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))

или

import sys

main(sys.stdin)        
3 Reinderien Oct 23 2020 at 01:58

Вот предлагаемая реализация, которая практически ничего не меняет в вашем алгоритме, но

  • имеет правильный отступ
  • использует небольшую подсказку типа
  • использует набор литералов и генераторы
  • использует _для "неиспользуемых" переменных
  • добавляет, parse_clause()потому что код предложения повторяется
  • использует StringIOдля этих целей, чтобы эффективно высмеивать stdinи использовать пример ввода, который вы показали
  • использует имена, совместимые с PEP8 (с подчеркиванием)
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')