约束满足问题(Constraint Satisfaction Problem, CSP)的Java实现(二)问题的定义
接上篇,我们开始直接代码部分的工作,首先是问题的定义,对于一个约束,我们有一个基类如下所示:
import java.util.ArrayList;
import java.util.List;
public class AbstractConstraint<V,C> {
List<V> variableList;
public AbstractConstraint(){
variableList = new ArrayList<>();
}
public AbstractConstraint(List<V> variables){
variableList = new ArrayList<>();
variableList.addAll(variables);
}
boolean satisfied(AbstractAssignment<V,C> assignment){
return true;
}
}
同理,定义一个抽象的解类,有
import java.util.HashMap;
import java.util.Map;
public class AbstractAssignment<V,D> {
Map<V,D> ass;
public AbstractAssignment(){
ass = new HashMap<>();
}
public AbstractAssignment(AbstractAssignment<V,D> input){
ass = new HashMap<>(input.getContents());
}
D getDomain(V v) {
return ass.get(v);
}
int getSize(){
return ass.size();
}
boolean hasVariable(V v){
return ass.containsKey(v);
}
void put(V v,D d){
ass.put(v,d);
}
void remove(V v){
ass.remove(v);
}
void display(){
for(Map.Entry<V,D> entry:ass.entrySet()){
System.out.println(entry.getKey().toString()+" "+entry.getValue().toString());
}
}
Map<V,D> getContents(){
return ass;
}
}
对于CSP问题,我们首先定义一个接口,这个接口包含了添加约束,验证是否合格,回溯求解等函数。
import java.util.List;
import java.util.Map;
public interface Solvable<V,D> {
void addConstraint(AbstractConstraint<V,D> constraint) throws Exception;
boolean consistent(V v, AbstractAssignment<V,D> assignment);
boolean backTracking(AbstractAssignment<V,D> assignment, Map<V, List<D>> domains);
AbstractAssignment<V,D> backTrackingSearch();
List<V> selectUnassignedVariable(List<V> unassigned);
}
CSP问题求解有一个基本的实现,如下
import java.util.*;
public class ClassicCSP<V,D> implements Solvable<V,D>{
List<V> variables;
Map<V, List<D>> domains;
Map<V, List<AbstractConstraint<V,D>>> constraints;
List<Oder<V,D>> oderAlgorithmList;
List<Inference<V,D>> inferenceList;
public ClassicCSP(List<V> vars, Map<V, List<D>> doms) throws Exception {
variables = new ArrayList<>();
constraints = new HashMap<>();
domains = new HashMap<>();
variables.addAll(vars);
domains.putAll(doms);
oderAlgorithmList = new ArrayList<>();
inferenceList = new ArrayList<>();
for(V v : variables){
if(!domains.containsKey(v)){
throw new Exception("The domain do not contain variable! Variable is "+v.toString());
}
}
}
public void addOderAlgorithm(Oder<V,D> oder){
oderAlgorithmList.add(oder);
}
public void addInferenceAlgorithm(Inference<V,D> inf) { inferenceList.add(inf); }
@Override
public void addConstraint(AbstractConstraint<V, D> constraint) throws Exception {
for(V v: constraint.variableList){
if(!variables.contains(v)){
throw new Exception("The variable in constraint is not in the CSP! Variable is "+v.toString());
}
else{
if(constraints.containsKey(v)){
constraints.get(v).add(constraint);
}
else{
constraints.put(v,new ArrayList<AbstractConstraint<V,D>>(Arrays.asList(constraint)));
}
}
}
}
@Override
public boolean consistent(V v, AbstractAssignment<V, D> assignment) {
if(constraints.get(v)!=null){
for(AbstractConstraint<V,D> constraint: constraints.get(v)){
if(!constraint.satisfied(assignment)){
return false;
}
}
}
return true;
}
@Override
public boolean backTracking(AbstractAssignment<V, D> assignment, Map<V, List<D>> domains) {
if(assignment.getSize()==variables.size()){
return true;
}
List<V> unassigned = new ArrayList<>();
for(V variable:variables){
if(!assignment.hasVariable(variable)){
unassigned.add(variable);
}
}
//order the variable!
for(Oder<V,D> oder:oderAlgorithmList){
boolean result = oder.oderUnassigned(unassigned, assignment, domains, constraints);
if(!result){
return false;
}
}
V firstVariable = unassigned.get(0);
unassigned.remove(firstVariable);
//order the variable!
for(D domain:domains.get(firstVariable)){
//to avoid operate on the same list, we need to deep copy
Map<V, List<D>> domainsBackend = new HashMap<>();
for(Map.Entry<V,List<D>> entry:domains.entrySet()){
domainsBackend.put(entry.getKey(),new ArrayList<>(entry.getValue()));
}
assignment.put(firstVariable, domain);
//inference!
if(consistent(firstVariable, assignment)){
for(Inference<V,D> infer:inferenceList){
infer.doInference(unassigned,assignment,domainsBackend,constraints);
}
//update the domain
for(V v:unassigned){
if(!v.equals(firstVariable)){
//get the variables not assigned
//get the variable that have constraint with firstVariable
for(AbstractConstraint<V,D> constraint:constraints.get(firstVariable)){
if(constraint.variableList.contains(v)){
for(D targetDomain:domains.get(v)){
AbstractAssignment<V,D> tempAssignment = new AbstractAssignment<>(assignment);
tempAssignment.put(v, targetDomain);
if(!consistent(firstVariable, tempAssignment)){
domainsBackend.get(v).remove(targetDomain);
}
}
}
}
}
}
boolean haveResult = backTracking(assignment, domainsBackend);
if(haveResult){
return true;
}
}
assignment.remove(firstVariable);
}
return false;
}
@Override
public AbstractAssignment<V, D> backTrackingSearch() {
AbstractAssignment<V,D> result = new AbstractAssignment<>();
boolean haveResult = backTracking(result, domains);
if(haveResult){
return result;
}
else{
return null;
}
}
@Override
public List<V> selectUnassignedVariable(List<V> unassigned) {
return null;
}
}
注意在这里,Inference和变量的排序都没有实现,我们通过装饰器模式讲这些功能灵活地加入CSP求解器中,具体如何实现看下一章。