3-SAT 솔버 Python

Oct 22 2020

이 프롬프트를 기반으로 3-SAT 솔버를 작성했습니다 .

Alice는 최근에 하드웨어 설계 회사에서 일하기 시작했으며 작업의 일부로 조립 된 집적 회로의 결함을 식별해야합니다. 이러한 결함을 식별하기위한 접근 방식은 만족도 인스턴스를 해결하는 것으로 귀결됩니다. 그녀는이 작업을 수행하는 프로그램을 작성하는 데 귀하의 도움이 필요합니다.

입력 입력
의 첫 번째 줄에는 따라야 할 테스트 케이스의 수를 나타내는 5 개 이하의 단일 정수가 포함됩니다. 각 테스트 케이스의 첫 번째 행에는 두 개의 정수 n과 m이 포함됩니다. 여기서 1 ≤ n ≤ 20은 변수 수를 나타내고 1 ≤ m ≤ 100은 절의 수를 나타냅니다. 그런 다음 m 줄이 각 절에 해당합니다. 각 절은 1 ≤ i ≤ n에 대한 Xi 또는 ~ Xi 형식의 리터럴 분리입니다. 여기서 ~ Xi는 리터럴 Xi의 부정을 나타냅니다. "or"연산자는 '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")

나는 들여 쓰기 된 for-loops 때문에 문제가 여기에 있다고 생각합니다. 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

전략적으로 배치하는 print 문을 사용하여 코드를 계측하면 반복되는 계산이 진행되고 있음을 알 수 있습니다. 두 번째 테스트 케이스에서 절을 처리 할 때 X2 v ~X3세트 {'X1', 'X2'}mySets두 번 추가됩니다 . 절을 처리 할 때 X3 v ~X1집합 {'X3', 'X1', 'X2'}mySets세 번 추가됩니다 .

큰 경우 에는 중복을 제거하기 위해 목록 대신 mySetsa 로 변경 하는 속도를 높일 수 있습니다 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')