backtracking_search_queens()
와
m-색칠하기 문제의 되추적 함수 backtracking_search_colors()
는
사실상 동일한 알고리즘을 사용함.promissing_queens()
와 promissing_colors()
가 서로 다름.backtracking_search()
함수의 매개변수(파라미터)로 추상화하여 사용 가능.promissing()
등으로 지정 가능.promissing
매개변수 추가: 유망성 확인 함수를 인자로 받음.promissing
매개변수가 기대하는 함수 인자의 유형(type)int
와 Dict[int, int]
bool
promissing
함수의 유형: Callable[[int, Dict[int, int]], bool]
constraint_queens.py
와
constraint_colors.py
저장되어 있음을 가정함.from typing import List, Dict, Callable
def backtracking_search(promissing: Callable[[int, Dict[int, int]], bool],
assignment: Dict[int, int] = {}):
"""assignment: 각각의 변수를 키로 사용하고 키값은 해당 변수에 할당될 값"""
# 모든 변수에 대한 값이 지정된 경우 조건을 만족시키는 해가 완성된 것임
if len(assignment) == len(variables):
return assignment
# 아직 값을 갖지 않은 변수들이 존재하면 되추적 알고리즘을 아직 할당되지 않은 값을 대상으로 이어서 진행
unassigned = [v for v in variables if v not in assignment]
first = unassigned[0]
for value in domains[first]:
# 주의: 기존의 assignment를 보호하기 위해 복사본 활용
# 되추적이 발생할 때 이전 할당값을 기억해 두기 위해서임.
local_assignment = assignment.copy()
local_assignment[first] = value
# local_assignment 값이 유망하면 재귀 호출을 사용하여 변수 할당 이어감.
if promissing(first, local_assignment):
result = backtracking_search(promissing, local_assignment)
# 유망성을 이어가지 못하면 되추적 실행
if result is not None:
return result
return None
# 변수: 네 개의 퀸 또는 네 마디의 번호, 즉, 1, 2, 3, 4
variables : List[int] = [1, 2, 3, 4]
# 도메인: 네 개의 퀸이 위치할 수 있는 가능한 모든 열 또는 각각의 마디에 사용될 수 있는 가능한 모든 색상
domains: Dict[int, List[int]] = {}
columns = [1, 2, 3, 4] # 네 개의 열 또는 색상
for var in variables:
domains[var] = columns
from constraint_queens import promissing
print(backtracking_search(promissing))
{1: 2, 2: 4, 3: 1, 4: 3}
from constraint_colors import promissing
print(backtracking_search(promissing))
{1: 1, 2: 2, 3: 3, 4: 2}
n-퀸과 m-색칠하기 문제에서 변수 또는 도메인 값들이 숫자가 아닌 문자열 등 다른 자료형이 사용될 수 있음.
체스판의 열은 원래 1에서 8까의 숫자 대신에 a 부터 h 사이의 알파벳으로 표시됨.
지도를 색칠할 때 영역은 번호 보다는 이름으로 표현함.
backtracking_search()
)는 변하지 않음.promissing()
)는 문제(인스턴스)별로 달라짐.제네릭 클래스의 사용법은 다음과 같음.
from typing import Generic, TypeVar
# 제네릭 변수. 여기서는 두 개의 제네릭 변수 지정
# 해당 클래스의 인스턴스를 선언하면 적절한 자료형으로 자동 대체됨.
V = TypeVar('V')
D = TypeVar('D')
# 아래 제네릭클래스에서 사용되는 변수 또는 값의 일부가 V 또는 D 자료형과 연관됨
class 제네릭클래스(Generic[V, D]):
클래스본문
promissing()
메서드는 추상메서드(abstract method)로 지정한 후
문제에 따라 다르게 정의해야 함.추상클래스와 추상메서드 사용법은 다음과 같음.
abc
모듈에서 ABC
클래스와 abstractmethod
장식자 불러오기추상클래스는 ABC
클래스를 상속해야 함.
from abc import ABC, abstractmethod
class 추상클래스명(ABC):
@abstractmethod
def 추상메서드(self, 매개변수, ..., 매개변수):
...
# 기타 메서드 및 속성
V
: 주어진 문제에 사용되는 변수들의 자료형. variables: List[V]
D
: 주어진 문제에 사용되는 도메인에 포함된 값들의 자료형. domains: List[D]
from typing import Generic, TypeVar, Dict, List, Optional
from abc import ABC, abstractmethod
V = TypeVar('V')
D = TypeVar('D')
Constraint
클래스 선언Generic
클래스와 ABC
클래스 모두 상속class Constraint(Generic[V, D], ABC):
@abstractmethod
def promissing(self, variable: V, assignment: Dict[V, D]) -> bool:
...
Backtracking
클래스 선언
Generic
클래스 상속주의: Optional
: 경우에 따라 None
이 값으로 사용될 수 있음을 암시함.
Optional[T]
의 의미: 기본적으로 자료형 T의 값을 사용하지만 None
이 사용될 수도 있음.class Backtracking(Generic[V,D]):
# 제약조건을 지정하면서 인스턴스 생성
def __init__(self,
variables: List[V],
domains: Dict[V, List[D]],
constraint: Constraint[V,D]) -> None:
self.variables: List[V] = variables
self.domains: Dict[V,List[D]] = domains
self.constraint = constraint
# promissing 메서드가 Constraint 클래스에서 선언되었기에 따로 인자로 받지 않음.
def backtracking_search(self,
assignment: Dict[V,D] = {}) -> Optional[Dict[V,D]]:
if len(assignment) == len(self.variables):
return assignment
unassigned: List[V] = [v for v in self.variables if v not in assignment]
first: V = unassigned[0]
for value in self.domains[first]:
local_assignment = assignment.copy()
local_assignment[first] = value
if constraint.promissing(first, local_assignment):
result = self.backtracking_search(local_assignment)
if result is not None:
return result
return None
promissing()
메서드 정의가
이전과 조금 달라짐.enumerate()
함수를 활용하여 항목의 인덱스를 활용할 수 있음.# 변수: 여덟 개의 퀸 1, 2, ..., 8
variables : List[int] = [1, 2, 3, 4, 5, 6, 7, 8]
# 도메인: 여덟 개의 퀸이 위치할 수 있는 가능한 모든 열. 문자열 사용
domains: Dict[int, List[str]] = {}
columns = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
for var in variables:
domains[var] = columns
# columns의 순서 지정하기
col2ind = {column : index+1 for (index, column) in enumerate(columns)}
Constraint
클래스의 인스턴스 생성하려면 먼저 Constraint
클래스를 상속하는 구상클래스를 선언해야 함.promissing()
메서드를 구현해야 함. promissing()
함수와 동일.col2ind
사전을 활용함class ConstraintQueens(Constraint[int, str]):
# promissing 메서드 정의해야 함.
def promissing(self, variable: int, assignment: Dict[int, str]) -> bool:
# q1r, q1c: 첫째 퀸이 놓인 마디의 열과 행
# 키값이 문자열이기에 해당 문자열의 인덱스를 대신 사용함. 즉, col2ind 활용
for q1r, q1c in assignment.items():
q1c = col2ind[assignment[q1r]] # 첫째 퀸의 열
# q2r = 첫째 퀸 아래에 위치한 다른 모든 퀸들을 대상으로 조건만족여부 확인
for q2r in range(q1r + 1, len(assignment) + 1):
q2c = col2ind[assignment[q2r]] # 둘째 퀸의 열
if q1c == q2c: # 동일 열에 위치?
return False
if abs(q1r - q2r) == abs(q1c - q2c): # 대각선상에 위치?
return False
# 모든 변수에 대해 제약조건 만족됨
return True
constraint = ConstraintQueens()
backtracking = Backtracking(variables, domains, constraint)
backtracking.backtracking_search()
{1: 'a', 2: 'e', 3: 'h', 4: 'f', 5: 'c', 6: 'g', 7: 'b', 8: 'd'}
# 변수: 네 개의 퀸 1, 2, 3, 4
variables : List[int] = [1, 2, 3, 4]
# 도메인: 네 개의 퀸이 위치할 수 있는 가능한 모든 열. 문자열 사용
domains: Dict[int, List[str]] = {}
columns = ['a', 'b', 'c', 'd']
for var in variables:
domains[var] = columns
# columns의 순서 지정하기
col2ind = {column : index+1 for (index, column) in enumerate(columns)}
constraint = ConstraintQueens()
backtracking = Backtracking(variables, domains, constraint)
backtracking.backtracking_search()
{1: 'b', 2: 'd', 3: 'a', 4: 'c'}
promissing()
함수와 동일.# 변수: 네 개의 퀸 또는 네 마디의 번호, 즉, 1, 2, 3, 4
variables : List[int] = [1, 2, 3, 4]
# 도메인: 네 개의 퀸이 위치할 수 있는 가능한 모든 열 또는 각각의 마디에 사용될 수 있는 가능한 모든 색상
domains: Dict[int, List[str]] = {}
columns = ['R', 'G', 'B']
for var in variables:
domains[var] = columns
class ConstraintColors(Constraint[int, str]):
# promissing 메서드 정의해야 함.
def promissing(self, variable: int, assignment: Dict[int, str]) -> bool:
# 각 마디에 대한 이웃마디의 리스트
constraints = {
1 : [2, 3, 4],
2 : [1, 3],
3 : [1, 2, 4],
4 : [1, 3]
}
for var in constraints[variable]:
if (var in assignment) and (assignment[var] == assignment[variable]):
return False
return True
constraint = ConstraintColors()
backtracking = Backtracking(variables, domains, constraint)
backtracking.backtracking_search()
{1: 'R', 2: 'G', 3: 'B', 4: 'G'}