├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.sbt ├── csp-core └── src │ └── main │ └── scala │ └── com │ └── mgu │ └── csp │ ├── AllDiff.scala │ ├── Assignment.scala │ ├── CSP.scala │ ├── Constraint.scala │ ├── DFSSolver.scala │ ├── MinimumRemainingValue.scala │ ├── NonMatchingDifference.scala │ ├── ValueOrdering.scala │ ├── Variable.scala │ └── VariableOrdering.scala ├── csp-queens └── src │ └── main │ └── scala │ └── com │ └── mgu │ └── csp │ └── queens │ ├── QueensApp.scala │ └── QueensCSP.scala ├── csp-sudoku └── src │ └── main │ └── scala │ └── com │ └── mgu │ └── csp │ └── sudoku │ ├── IdGenerator.scala │ ├── SudokuApp.scala │ └── SudokuCSP.scala └── project ├── build.properties └── plugins.sbt /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | .idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | scala: 3 | - 2.11.7 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Markus Günther 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Constraint Satisfaction Problem Solver 2 | 3 | [![Build Status](https://travis-ci.org/mguenther/csp-solver-scala.svg?branch=master)](https://travis-ci.org/mguenther/csp-solver-scala.svg) 4 | 5 | This is a Scala-based port of the CSP solver `csp-solver-java`. It is a tiny framework for solving constraint 6 | satisfaction problems (CSP) that have both discrete and finite domains. Although this framework is fully 7 | functional, it is not meant to be production-ready, as it is primarily a small exercise for myself on 8 | how to do functional programming with Scala. 9 | 10 | The implementation is largely inspired from the splendid introductory text "Artificial Intelligence - A Modern Approach" 11 | by Stuart Russell and Peter Norvig. 12 | 13 | # Module Overview 14 | 15 | * `csp-core`: Contains a functional approach on CSP-framework design for CSPs that are both discrete and finite. 16 | * `csp-queens`: Contains an example application which expresses the N-Queens problem as a CSP. 17 | * `csp-sudoku`: Contains an example application which expresses Sudoku as a CSP. 18 | 19 | # Design 20 | 21 | The framework builds upon a set of a few abstractions. Trait `CSP` is the basis for all domain-specific 22 | CSPs. It provides the means to construct the initial assignment, which is comprised of all the `Variable`s of the CSP 23 | and their initial state. A variable can be either *assigned* or *unassigned*. In the first case, its domain has been 24 | reduced to a single fixed value, while in the latter case it has no assigned value, but a - possibly reduced - set 25 | of admissible domain values. Trait `CSP` also provides the means to construct the full set of `Constraint`s for 26 | the CSP. A `Variable` of the CSP takes part in one or multiple `Constraint`s. A `Constraint` involves some subset 27 | of the variables of a CSP and specifies the allowable combinations of values for that subset. The `Constraint` 28 | trait provides the means to determine whether a constraint is *consistent* and *satisfied* given the set of 29 | dependent variables. 30 | 31 | The current state of a CSP is represented using an `Assignment`. An assignment always contains the full set of 32 | variables of the CSP. An `Assignment` is *partial*, if it still contains `Variable`s that are unassigned. An assignment 33 | that does not violate any constraints is called *consistent* or *legal*. A *complete* assignment is one in which 34 | every variable is assigned. A solution to the CSP is a *complete* assignment which does not violate any constraints. 35 | Class `Assignment` implements *forward checking*. This is a technique that eliminates the value assigned to a variable 36 | from all other variables that participate in the same `Constraint`s, thus further decreasing the search space of CSP. 37 | 38 | Class `DFSSolver` provides a generic way to operate on instances of `CSP` using depth-first search. It is able to apply 39 | heuristics for both variable ordering and value ordering that can dramatically reduce the search space. By default, it 40 | uses an uninformed approach that simply selects the next unassigned variable and the preserves the original ordering of 41 | domain values for that variable. The `DFSSolver` progresses from `Assignment` to its successor until a complete 42 | assignment has been found or the search space is exhausted. 43 | 44 | ## Constraints 45 | 46 | The framework currently only provides the `AllDiff` constraint. This constraint is satisfied if each of its variables is 47 | assigned to a different value. 48 | 49 | ## Variable Ordering 50 | 51 | The `DFSSolver` uses an uninformed approach by default which simply selects the next unassigned variable. However, 52 | it is also possible to use the `MinimumRemainingValue` heuristic, which selects the variable that is most constrained 53 | given the current state of the CSP. Thus, the variable that has the fewest choices for domain values left will be 54 | chosen. Using this heuristic can dramatically reduce the runtime of the solver, since the search space is pruned 55 | efficiently. 56 | 57 | ## Value Ordering 58 | 59 | The `DFSSolver` uses an uninformed approach by default which simply preserves the original ordering of domain values 60 | for a given unassigned `Variable`. Currently, there is no informed implementation of `ValueOrdering`. 61 | 62 | # Noteworthy things about this port 63 | 64 | The experience of rewriting a Java-based framework that makes heavy use of functional concepts into an equivalent 65 | Scala solution was quite interesting. I have to say that I was and still am pretty happy on how the Java-based 66 | solution turned out. Using the Stream API and concepts from functional programming like immutability and combinator 67 | functions significantly improved the quality of the code. 68 | 69 | Still, the Java version has some shortcomings which the Scala-based solution solves much more elegantly. First, 70 | there is the need to do an excessive amount of defensive copying in order to implement immutable classes. Second, 71 | the Stream API lacks combinator functions like `foldLeft`, which greatly simplify multiple state mutations in the 72 | presence of immutable data structures. 73 | 74 | While the Scala version resembles the Java version in almost every detail, these aspects improved the readability 75 | of the code even more. The next couple of sections highlight some of the aspects that contribute to the Java vs. 76 | Scala discussion in greater detail, but also provide general things about this port that may be of interest. 77 | 78 | ## Use a type alias to represent a typed identity 79 | 80 | The Java version uses the immutable class `VariableIdentity` to represent the identity of a variable using a specific 81 | type rather than `String`. But the extra class increases the amount of code and is at its core just a wrapper around a 82 | `String`. The Scala version makes use of type aliases which eliminates the need for a separate class while enabling us 83 | to use a specific type. 84 | 85 | So, in Scala we simply write 86 | 87 | object Variable { 88 | type Identity = String 89 | } 90 | 91 | instead of providing our own class for this, like in the Java version. 92 | 93 | public class VariableIdentity { 94 | 95 | private final String identity; 96 | 97 | private VariableIdentity(final String identity) { 98 | this.identity = identity; 99 | } 100 | 101 | @Override 102 | public boolean equals(Object o) { 103 | if (this == o) return true; 104 | if (o == null || getClass() != o.getClass()) return false; 105 | 106 | VariableIdentity that = (VariableIdentity) o; 107 | 108 | return !(identity != null ? !identity.equals(that.identity) : that.identity != null); 109 | } 110 | 111 | @Override 112 | public int hashCode() { 113 | return identity != null ? identity.hashCode() : 0; 114 | } 115 | 116 | @Override 117 | public String toString() { 118 | return identity; 119 | } 120 | 121 | public static VariableIdentity id(final String identity) { 122 | return new VariableIdentity(identity); 123 | } 124 | } 125 | 126 | ## Use case classes with an identity 127 | 128 | A `Variable` is represented using a case class to leverage its immutability constraints and copy constructors. 129 | However, since a variable also has a concept of *identity* the auto-generated methods for `equals` and `hashCode` 130 | are no longer suitable, since they are evaluated against the whole set of attributes that are declared with the 131 | *first* parameter list of the case class. Splitting the set of attributes into two parameter lists allows us 132 | to implement case classes that have an identity, while retaining their immutability constraints. Have a look at 133 | the implementation of `Variable`. 134 | 135 | case class Variable[+A](id: Identity)(val value: Option[A] = None, val domain: List[A]) { 136 | def restrict[B >: A](disallowedValue: B): Variable[B] = 137 | copy()(value = value, domain = domain.filterNot(items => items == disallowedValue)) 138 | 139 | def assign[B >: A](value: B): Variable[B] = 140 | copy()(value = Some(value), domain = List.empty) 141 | 142 | def isAssigned(): Boolean = 143 | value.isDefined 144 | } 145 | 146 | This comes at the price of a slightly higher level of verbosity. Attributes of the case class that go into the second 147 | parameter have to be explicitly marked as `val`s. Otherwise, accessing the attribute like `Variable.value` would not 148 | work. Also, copy constructors get a bit more complicated, since the whole set of attributes that went into the second 149 | parameter list (in this case `value` and `domain`) need to be explicitly named. 150 | 151 | ## Use of `foldLeft` when applying multiple state mutations 152 | 153 | When restricting the domains of dependent variables, we are forced to use a `for`-loop in the Java version in order 154 | to successively mutate the intermediary `Assignment`s since the Java Stream API does not provide a function that acts 155 | as a reducer and combiner. 156 | 157 | for (VariableIdentity variableIdentity : dependentVariables(variable, constraints)) { 158 | assignment = assignment.restrict(variableIdentity, value); 159 | } 160 | 161 | Scala does provide such a function: `foldLeft`. So instead of mixing imperative and functional programming like in the 162 | Java version, we can simply write 163 | 164 | dependentVariables(variableIdentity, constraints) 165 | .foldLeft(baseAssignment)((assignment, dependentVariableIdentity) => assignment.restrict(dependentVariableIdentity, value)) 166 | 167 | and stay functional all the way through. 168 | 169 | ## Set is invariant in its parametric type 170 | 171 | The immutable `Set` that comes with Scala is invariant in its parametric type. Thus, I switched from the immutable 172 | `Set` to an immutable `List` whenever appropriate. I'd be glad on feedback if there is a better way of doing this. 173 | 174 | # Example Application: Sudoku as CSP 175 | 176 | Package `com.mgu.csp.sudoku` formulates Sudoku as a constraint satisfaction problem. The current implementation is able to parse 177 | a Sudoku puzzle as line-delimited string like the one shown underneath. 178 | 179 | 003020600 180 | 900305001 181 | 001806400 182 | 008102900 183 | 700000008 184 | 006708200 185 | 002609500 186 | 800203009 187 | 005010300 188 | 189 | Each cell of the Sudoku puzzle is represented as a variable of the CSP, where 190 | `0` denotes an unassigned variable with domain values ranging from 1 to 9 and where every other number represents an 191 | assigned variable. There are three kinds of constraints, which are all represented using `AllDiff` on their dependent 192 | variables: 193 | 194 | * Row constraints: The assigned values to every variable in a row of the puzzle must be all different. 195 | * Column constraints: The assigned values to every variable in a column of the puzzle must be all different. 196 | * Grid constraints: The assigned values to every variable within a grid must be all different. 197 | 198 | In total there are 27 constraints and 81 variables. 199 | 200 | # Example Application: N-Queens as CSP 201 | 202 | Package `com.mgu.csp.queens` represents the N-Queens problem as a constraint satisfaction problem. The input for the 203 | CSP is the size of the board (defaults to 8) that starts off with an `Assignment` that contains only unassigned 204 | variables. A `Variable` represents a single row of the board, while its domain represents possible column assignments. 205 | 206 | The Queens CSP uses the `AllDiff` constraint on each pair of rows. This enables us to filter all solutions in which 207 | two rows use the same column for their queen placement. To also filter out diagonally conflicting queens, we make us 208 | of the so called `NonMatchingDifference` constraint. This constraint works only if both dependent variables are already 209 | assigned a value. If not, it cannot decide whether to discard the assignment. As this property holds for all 210 | constraints of that type, it is not able to prune a partial assignment. This limits the efficiency of our CSP 211 | representation as it is only able to prune a partial assignment if the `AllDiff` constraint takes action. 212 | 213 | The output for a solution of the CSP looks like this (N=8): 214 | 215 | ...Q.... [4] 216 | .Q...... [2] 217 | .......Q [8] 218 | .....Q.. [6] 219 | Q....... [1] 220 | ..Q..... [3] 221 | ....Q... [5] 222 | ......Q. [7] 223 | 224 | # Contribution 225 | 226 | Feedback and pull requests are always welcome! 227 | 228 | Contributors: 229 | 230 | * [Markus Hauck](https://github.com/markus1189) 231 | 232 | # License 233 | 234 | This software is released under the terms of the MIT license. -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | lazy val commonSettings = Seq( 2 | organization := "com.mgu", 3 | version := "0.1.0", 4 | scalaVersion := "2.11.7" 5 | ) 6 | 7 | lazy val csp = (project in file("csp-core")) 8 | .settings(commonSettings: _*) 9 | 10 | lazy val sudoku = (project in file("csp-sudoku")) 11 | .settings(commonSettings: _*) 12 | .dependsOn(csp) 13 | 14 | lazy val queens = (project in file("csp-queens")) 15 | .settings(commonSettings: _*) 16 | .dependsOn(csp) -------------------------------------------------------------------------------- /csp-core/src/main/scala/com/mgu/csp/AllDiff.scala: -------------------------------------------------------------------------------- 1 | package com.mgu.csp 2 | 3 | import com.mgu.csp.Variable.Identity 4 | 5 | import scala.annotation.tailrec 6 | 7 | /** 8 | * The `AllDiff` constraint ensures that assigned variables the constraint relies on hold a unique value 9 | * with regard to each other. It also ensures that unassigned variables are not in a conflicting state. Suppose 10 | * variables X and Y are unassigned and share the same restricted domain of values D(X) = D(Y) = { c }. Both X 11 | * and Y are in a conflicted state and it is impossible to satisfy the constraint by further variable assignments. 12 | * 13 | * @param reliesOn 14 | * represents the identities of all [[Variable]]s that this `Constraint` relies on 15 | * 16 | * @author Markus Günther (markus.guenther@gmail.com) 17 | */ 18 | case class AllDiff[+A](reliesOn: List[Identity]) extends Constraint[A] { 19 | 20 | override def isConsistent[B >: A](dependentVariables: List[Variable[B]]): Boolean = { 21 | noDuplicatesAssigned(dependentVariables) && 22 | unassignedCanBeAssigned(dependentVariables) && 23 | noConflictingUnassigned(dependentVariables) 24 | } 25 | 26 | private def noDuplicatesAssigned[B >: A](dependentVariables: List[Variable[B]]): Boolean = { 27 | val assignedValues = dependentVariables 28 | .filter(variable => variable.isAssigned()) 29 | .map(variable => variable.value) 30 | !containsDuplicates(assignedValues) 31 | } 32 | 33 | private def unassignedCanBeAssigned[B >: A](dependentVariables: List[Variable[B]]): Boolean = { 34 | val assignedValues = dependentVariables 35 | .filter(variable => variable.isAssigned()) 36 | .map(variable => variable.value) 37 | dependentVariables 38 | .filterNot(variable => variable.isAssigned()) 39 | .map(variable => variable.domain) 40 | .forall(candidates => candidates.exists(candidate => !assignedValues.contains(candidate))) 41 | } 42 | 43 | private def noConflictingUnassigned[B >: A](dependentVariables: List[Variable[B]]): Boolean = { 44 | val remainingValues = dependentVariables 45 | .filterNot(variable => variable.isAssigned()) 46 | .collect { case v @ Variable(_) if v.domain.size == 1 => v.domain.head } 47 | !containsDuplicates(remainingValues) 48 | } 49 | 50 | @tailrec 51 | private def containsDuplicates[B](list: List[B], seen: Set[B] = Set[B]()): Boolean = 52 | list match { 53 | case x :: xs => if (seen.contains(x)) true else containsDuplicates(xs, seen + x) 54 | case _ => false 55 | } 56 | 57 | override def isSatisfied[B >: A](dependentVariables: List[Variable[B]]): Boolean = { 58 | lazy val allVariablesAssigned = dependentVariables.forall(variable => variable.isAssigned()) 59 | allVariablesAssigned && isConsistent(dependentVariables) 60 | } 61 | } -------------------------------------------------------------------------------- /csp-core/src/main/scala/com/mgu/csp/Assignment.scala: -------------------------------------------------------------------------------- 1 | package com.mgu.csp 2 | 3 | import com.mgu.csp.Variable.Identity 4 | 5 | /** 6 | * A state of the CSP is defined by an assignment of values to some or all of the 7 | * variables. An assignment that does not violate any constraints is called a consistent 8 | * or legal assignment. A complete assignment is one in which every variable is mentioned. 9 | * A solution to the CSP is a complete assignment which does not violate any constraints. 10 | * 11 | * This `Assignment` applies forward checking. Thus, whenever a [[Variable]] is 12 | * assigned to a value, that value is removed from the domain of all dependent unassigned 13 | * variables. 14 | * 15 | * @author Markus Günther (markus.guenther@gmail.com) 16 | */ 17 | case class Assignment[+A](variableAssignments: Map[Identity, Variable[A]]) { 18 | /** 19 | * An assignment that does not violate any constraint is called consistent. 20 | * 21 | * @param constraints 22 | * [[List]] of [[Constraint]]s of a [[CSP]] that this particular assignment 23 | * should be checked against for consistency 24 | * @return 25 | * `true` if this assignment is consistent with regard to the given [[Constraint]]s, 26 | * `false` otherwise 27 | */ 28 | def isConsistent[B >: A](constraints: List[Constraint[B]]): Boolean = 29 | constraints.forall(constraint => constraint.isConsistent(subsetOf(constraint.reliesOn()))) 30 | 31 | /** 32 | * A complete assignment is one in which every variable of the [[CSP]] is mentioned. 33 | * 34 | * @return 35 | * `true` if this assignment is complete with regard to the given [[List]] of 36 | * [[Variable]]s, `false` otherwise 37 | */ 38 | def isComplete(): Boolean = 39 | variableAssignments.forall { case (id, variable) => variable.isAssigned() } 40 | 41 | /** 42 | * A solution to a [[CSP]] is a complete assignment that satisfies all [[Constraint]]s. 43 | * 44 | * @param constraints 45 | * [[List]] of [[Constraint]]s of a [[CSP]] that this particular assignment 46 | * should be checked against 47 | * @return 48 | * `true` if this assignment is satisfied with regard to the given [[Constraint]]s, 49 | * `false` otherwise 50 | */ 51 | def isSatisfied[B >: A](constraints: List[Constraint[B]]): Boolean = 52 | isComplete && constraints.forall(constraint => constraint.isSatisfied(subsetOf(constraint.reliesOn()))) 53 | 54 | private def subsetOf(variableIdentities: List[Identity]): List[Variable[A]] = 55 | variableIdentities.flatMap(variableIdentity => variableAssignments.get(variableIdentity)) 56 | 57 | /** 58 | * Assigns the value of type `B >: A` to the given variable. The value must be in the domain of that 59 | * variable, otherwise the assignment will fail. 60 | * 61 | * @param variableIdentity 62 | * identifies the variable that the given value will be assigned to 63 | * @param value 64 | * this is the value that will be assigned to the variable 65 | * @return 66 | * copy of this `Assignment` with the additional variable assignment based on the given parameters 67 | */ 68 | def assign[B >: A](variableIdentity: Identity, value: B, constraints: List[Constraint[B]]): Assignment[B] = { 69 | val modifiedVariable = variableAssignments 70 | .get(variableIdentity) 71 | .map(variable => variable.assign(value)) 72 | .get 73 | val baseAssignment = copy(variableAssignments = variableAssignments + ((variableIdentity, modifiedVariable))) 74 | dependentVariables(variableIdentity, constraints) 75 | .foldLeft(baseAssignment)((assignment, dependentVariableIdentity) => assignment.restrict(dependentVariableIdentity, value)) 76 | } 77 | 78 | private def dependentVariables[B >: A](variableIdentity: Identity, constraints: List[Constraint[B]]): List[Identity] = 79 | constraints 80 | .filter(constraint => constraint.reliesOn().contains(variableIdentity)) 81 | .flatMap(dependentConstraint => dependentConstraint.reliesOn()) 82 | .filterNot(identity => identity == variableIdentity) 83 | .filterNot(identity => variableAssignments.get(identity).exists(variable => variable.isAssigned())) 84 | 85 | /** 86 | * Restricts the domain of the given variable by removing the given value from its domain. 87 | * 88 | * Please note that any variable that has a restricted set of possible domain values and not a fixed assignment 89 | * is an unassigned variable. 90 | * 91 | * @param variableIdentity 92 | * uniquely identifies a variable within this `Assignment` 93 | * @param value 94 | * the value that ought to be removed from the domain of the variable identified by the given identity 95 | * @return 96 | * copy of this `Assignment` with the updated domain for the referenced variable 97 | */ 98 | def restrict[B >: A](variableIdentity: Identity, value: B): Assignment[B] = { 99 | val restrictedVariable = variableAssignments 100 | .get(variableIdentity) 101 | .map(variable => variable.restrict(value)) 102 | .get 103 | copy(variableAssignments = variableAssignments + ((variableIdentity, restrictedVariable))) 104 | } 105 | 106 | /** 107 | * @return 108 | * immutable [[List]] of all [[Variable]]s that are not assigned. 109 | */ 110 | def unassignedVariables(): List[Variable[A]] = 111 | variableAssignments 112 | .values 113 | .filterNot(variable => variable.isAssigned()) 114 | .toList 115 | } -------------------------------------------------------------------------------- /csp-core/src/main/scala/com/mgu/csp/CSP.scala: -------------------------------------------------------------------------------- 1 | package com.mgu.csp 2 | 3 | /** 4 | * This is the abstract representation of a CSP. A CSP is defined by a set of variables X_i (through 5 | * its initial assignment) and a set of constraints. It provides boolean accessors to determine whether 6 | * a given assignment is consistent or satisfied with its definition. A solution to the CSP is an 7 | * assignment that is both complete and satisfies all constraints. 8 | * 9 | * @author Markus Günther (markus.guenther@gmail.com) 10 | */ 11 | trait CSP[+A] { 12 | /** 13 | * Determines whether the given [[Assignment]] is consistent with regard to the [[Constraint]]s 14 | * of this CSP. 15 | * 16 | * @param assignment 17 | * an [[Assignment]] represents the current state of this CSP 18 | * @return 19 | * `true` if the given [[Assignment]] is consistent, `false` otherwise 20 | */ 21 | final def isConsistent[B >: A](assignment: Assignment[B]): Boolean = { 22 | assignment.isConsistent(constraints()) 23 | } 24 | 25 | /** 26 | * Determines whether the given [[Assignment]] is satisfied with regard to the [[Constraint]]s 27 | * of this CSP. 28 | * 29 | * @param assignment 30 | * an [[Assignment]] represents the current state of this CSP 31 | * @return 32 | * `true` if the given [[Assignment]] is satisfied, `false` otherwise 33 | */ 34 | final def isSatisfied[B >: A](assignment: Assignment[B]): Boolean = 35 | assignment.isSatisfied(constraints()) 36 | 37 | /** 38 | * @return 39 | * Yields the set of constraints for this CSP 40 | */ 41 | def constraints(): List[Constraint[A]] 42 | 43 | /** 44 | * @return 45 | * Yields the initial assignment for this CSP 46 | */ 47 | def initialAssignment(): Assignment[A] 48 | } -------------------------------------------------------------------------------- /csp-core/src/main/scala/com/mgu/csp/Constraint.scala: -------------------------------------------------------------------------------- 1 | package com.mgu.csp 2 | 3 | import com.mgu.csp.Variable.Identity 4 | 5 | /** 6 | * A `Constraint` involves some subset of the variables of a CSP and specifies the allowable 7 | * combinations of values for that subset. 8 | * 9 | * @author Markus Günther (markus.guenther@gmail.com) 10 | */ 11 | trait Constraint[+A] { 12 | /** 13 | * Determines whether this `Constraint` is consistent with the current state of its dependent 14 | * variables. 15 | * 16 | * @param dependentVariables 17 | * Subset of variables of the CSP that this `Constraint` relies on. The variables within 18 | * this subset represent a subset of the current state (cf. [[Assignment]]) of the CSP. 19 | * @return 20 | * `true` if this `Constraint` is consistent with the given [[Variable]]s, `false` otherwise 21 | */ 22 | def isConsistent[B >: A](dependentVariables: List[Variable[B]]): Boolean 23 | 24 | /** 25 | * Determines whether this `Constraint` is satisfied with the current state of its dependent 26 | * variables. 27 | * 28 | * @param dependentVariables 29 | * Subset of variables of the CSP that this `Constraint` relies on. The variables within 30 | * this subset represent a subset of the current state (cf. [[Assignment]]) of the CSP. 31 | * @return 32 | * `true` if this `Constraint` is satisfied with the given [[Variable]]s, `false` otherwise 33 | */ 34 | def isSatisfied[B >: A](dependentVariables: List[Variable[B]]): Boolean 35 | 36 | /** 37 | * @return 38 | * immutable [[List]] of [[Identity]] that identifies the set of variables this 39 | * particular `Constraint` relies on 40 | */ 41 | def reliesOn(): List[Identity] 42 | } -------------------------------------------------------------------------------- /csp-core/src/main/scala/com/mgu/csp/DFSSolver.scala: -------------------------------------------------------------------------------- 1 | package com.mgu.csp 2 | 3 | /** 4 | * Performs a backtracking kind of search by progressing along the state space in a depth-first manner. 5 | * The solver can make use of user-supplied heuristics for picking the next unassigned variable 6 | * (cf. [[VariableOrdering]]) and ordering the set of domain values for such an unassigned variable 7 | * (cf. [[ValueOrdering]]). 8 | * 9 | * By default, this backtracking uses uninformed heuristics for [[VariableOrdering]] and 10 | * [[ValueOrdering]]. 11 | * 12 | * @author Markus Günther (markus.guenther@gmail.com) 13 | */ 14 | class DFSSolver[+A]( 15 | val variableOrdering: VariableOrdering[A] = new UninformedVariableOrdering[A](), 16 | val valueOrdering: ValueOrdering[A] = new UninformedValueOrdering[A]()) { 17 | /** 18 | * Solves the given CSP by performing a depth-first search starting off from the initial state (the 19 | * initial assignment). 20 | * 21 | * @param csp 22 | * represents the CSP to solve 23 | * @return 24 | * `Some` [[Assignment]] that is completed, or `None` if no such [[Assignment]] can be found 25 | */ 26 | final def solve[B >: A](csp: CSP[B]): Option[Assignment[B]] = solve(csp, csp.initialAssignment()) 27 | 28 | private def solve[B >: A](csp: CSP[B], assignment: Assignment[B]): Option[Assignment[B]] = 29 | csp.isSatisfied(assignment) match { 30 | case true => Some(assignment) 31 | case _ => 32 | val unassignedVariableOpt = variableOrdering.selectUnassignedVariable(assignment) 33 | lazy val constraints = csp.constraints() 34 | 35 | unassignedVariableOpt.flatMap { unassignedVariable => 36 | valueOrdering 37 | .orderedDomain(unassignedVariable, constraints) 38 | .toStream // crucial, otherwise this will be solved using a BFS which is painfully inefficient 39 | .map(value => assignment.assign(unassignedVariable.id, value, constraints)) 40 | .filter(assignment => csp.isConsistent(assignment)) 41 | .flatMap(consistentAssignment => solve(csp, consistentAssignment)) 42 | .headOption 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /csp-core/src/main/scala/com/mgu/csp/MinimumRemainingValue.scala: -------------------------------------------------------------------------------- 1 | package com.mgu.csp 2 | 3 | /** 4 | * This `VariableOrdering` selects the unassigned [[Variable]] that has the fewest legal values 5 | * left. It is also known as the "most-constrained value" or "fail-first" heuristic, because it 6 | * select a variable that is most likely to cause a failure soon, thereby pruning the search tree. 7 | * 8 | * @author Markus Günther (markus.guenther@gmail.com) 9 | */ 10 | class MinimumRemainingValue[+A] extends VariableOrdering[A] { 11 | 12 | override def selectUnassignedVariable[B >: A](assignment: Assignment[B]): Option[Variable[B]] = 13 | assignment 14 | .unassignedVariables() 15 | .sortWith(mostConstrainedOf) 16 | .headOption 17 | 18 | private def mostConstrainedOf[B >: A](a: Variable[B], b: Variable[B]) = 19 | (a.domain.size - b.domain.size) < 0 20 | } -------------------------------------------------------------------------------- /csp-core/src/main/scala/com/mgu/csp/NonMatchingDifference.scala: -------------------------------------------------------------------------------- 1 | package com.mgu.csp 2 | 3 | import com.mgu.csp.Variable.Identity 4 | 5 | /** 6 | * The `NonMatchingDifference` constraint computes the difference over a list of dependent 7 | * variables, where the head value of that list is the basis from which each value of the tail 8 | * gets substracted. It then compares if the resulting difference is not equal to a given 9 | * difference. 10 | * 11 | * Please note that this constraint is only able to obtain a result if all dependent variables 12 | * are assigned. Thus, it checks for that condition and shortcircuits any state in which there 13 | * is at least one unassigned value by yielding `true`, so that a solver algorithm can progress. 14 | * 15 | * @param reliesOn 16 | * represents the identities of all [[Variable]]s that this `Constraint` relies on 17 | * @param differenceMustNotMatch 18 | * represents the non-matching difference 19 | * 20 | * @author Markus Günther (markus.guenther@gmail.com) 21 | */ 22 | case class NonMatchingDifference(reliesOn: List[Identity], differenceMustNotMatch: Int) extends Constraint[Int] { 23 | 24 | override def isConsistent[B >: Int](dependentVariables: List[Variable[B]]): Boolean = 25 | anyUnassigned(dependentVariables) || nonMatchingDifference(dependentVariables) 26 | 27 | private def anyUnassigned[B >: Int](dependentVariables: List[Variable[B]]): Boolean = 28 | dependentVariables.exists(variable => !variable.isAssigned()) 29 | 30 | private def nonMatchingDifference[B >: Int](dependentVariables: List[Variable[B]]): Boolean = { 31 | val values = dependentVariables.map(variable => variable.value) 32 | val difference = values 33 | .tail 34 | .foldLeft[Int](values.head.get.asInstanceOf[Int])((partialDifference, b) => partialDifference - b.get.asInstanceOf[Int]) 35 | difference != differenceMustNotMatch 36 | } 37 | 38 | override def isSatisfied[B >: Int](dependentVariables: List[Variable[B]]): Boolean = { 39 | lazy val allVariablesAssigned = dependentVariables.forall(variable => variable.isAssigned()) 40 | allVariablesAssigned && isConsistent(dependentVariables) 41 | } 42 | } -------------------------------------------------------------------------------- /csp-core/src/main/scala/com/mgu/csp/ValueOrdering.scala: -------------------------------------------------------------------------------- 1 | package com.mgu.csp 2 | 3 | /** 4 | * A `ValueOrdering` implements a strategy to order the domain of any given [[Variable]]. 5 | * 6 | * @author Markus Günther (markus.guenther@gmail.com) 7 | */ 8 | trait ValueOrdering[+A] { 9 | /** 10 | * Orders the remaining set of domain values of the given [[Variable]] with regard to the 11 | * implemented strategy. Implementing classes can make use of the set of [[Constraint]]s 12 | * to make better decisions on how to order the domain. 13 | * 14 | * @param variable 15 | * this is the variable whose domain shall be ordered with regard to the 16 | * implemented strategy 17 | * @param constraints 18 | * [[List]] of [[Constraint]]s of a CSP that this value ordering strategy can make use of 19 | * to make informed decisions on how to order the domain of the given [[Variable]] 20 | * @return 21 | * ordered [[List]] of [[Variable]]s 22 | */ 23 | def orderedDomain[B >: A](variable: Variable[B], constraints: List[Constraint[B]]): List[B] 24 | } 25 | 26 | /** 27 | * This is the default implementation of [[ValueOrdering]]. It preserves the original order 28 | * of domain values from the given [[Variable]]. 29 | */ 30 | class UninformedValueOrdering[+A] extends ValueOrdering[A] { 31 | override def orderedDomain[B >: A](variable: Variable[B], constraints: List[Constraint[B]]): List[B] = 32 | variable.domain 33 | } -------------------------------------------------------------------------------- /csp-core/src/main/scala/com/mgu/csp/Variable.scala: -------------------------------------------------------------------------------- 1 | package com.mgu.csp 2 | 3 | import com.mgu.csp.Variable.Identity 4 | 5 | /** 6 | * Each variable X_i in a CSP has a non-empty domain D_i of possible values. Domain values are discrete and finite. 7 | * Variables can be part of a partial assignment. 8 | * 9 | * @author Markus Günther (markus.guenther@gmail.com) 10 | */ 11 | case class Variable[+A](id: Identity)(val value: Option[A] = None, val domain: List[A]) { 12 | /** 13 | * Removes the given value from the domain of possible values. This operation is only admissible if the 14 | * variable is still unassigned. This method will immediately return if the domain does not contain the 15 | * given value, since we are already in a converged state with regard to this operation. 16 | * 17 | * @param disallowedValue 18 | * the value that ought to be removed from the set of possible domain values 19 | * @return 20 | * copy of this `Variable` with an updated set of remaining domain values 21 | */ 22 | def restrict[B >: A](disallowedValue: B): Variable[B] = 23 | copy()(value = value, domain = domain.filterNot(items => items == disallowedValue)) 24 | 25 | /** 26 | * Assigns the given value to the variable and clears its list of domain values. 27 | * 28 | * @param value 29 | * the value that is assigned to this { @code Variable} 30 | * @return 31 | * copy of this `Variable` with an assigned value and cleared domain values 32 | */ 33 | def assign[B >: A](value: B): Variable[B] = 34 | copy()(value = Some(value), domain = List.empty) 35 | 36 | def isAssigned(): Boolean = 37 | value.isDefined 38 | } 39 | 40 | object Variable { 41 | type Identity = String 42 | } -------------------------------------------------------------------------------- /csp-core/src/main/scala/com/mgu/csp/VariableOrdering.scala: -------------------------------------------------------------------------------- 1 | package com.mgu.csp 2 | 3 | /** 4 | * A `VariableOrdering` implements a strategy to fetch the next unassigned [[Variable]]. 5 | * 6 | * @author Markus Günther (markus.guenther@gmail.com) 7 | */ 8 | trait VariableOrdering[+A] { 9 | /** 10 | * Selects the next unassigned variable of the given [[Assignment]] with regard to the 11 | * implemented strategy. 12 | * 13 | * @param assignment 14 | * represents the current state of a CSP 15 | * @return 16 | * unassigned variable 17 | */ 18 | def selectUnassignedVariable[B >: A](assignment: Assignment[B]): Option[Variable[B]] 19 | } 20 | 21 | /** 22 | * This is the default implementation of [[VariableOrdering]]. It simply selects the 23 | * next unassigned variable with regard to the current state in [[Assignment]]. 24 | */ 25 | class UninformedVariableOrdering[+A] extends VariableOrdering[A] { 26 | override def selectUnassignedVariable[B >: A](assignment: Assignment[B]): Option[Variable[B]] = 27 | assignment.unassignedVariables().headOption 28 | } -------------------------------------------------------------------------------- /csp-queens/src/main/scala/com/mgu/csp/queens/QueensApp.scala: -------------------------------------------------------------------------------- 1 | package com.mgu.csp.queens 2 | 3 | import com.mgu.csp.DFSSolver 4 | import com.mgu.csp.queens.QueensCSP.id 5 | 6 | object QueensApp { 7 | 8 | def main(args: Array[String]): Unit = { 9 | val size = 8 10 | val queensCSP = new QueensCSP(size) 11 | val solver = new DFSSolver[Int]() 12 | val start = System.nanoTime() 13 | val assignment = solver.solve(queensCSP) 14 | val duration = (System.nanoTime() - start) / 1000000 15 | 16 | if (assignment.isEmpty) { 17 | println("Found no solution.") 18 | } else { 19 | println("Took " + duration + " ms.") 20 | println() 21 | 22 | (1 to size) 23 | .map(id) 24 | .foreach(row => { 25 | val queensPosition = assignment.get.variableAssignments.get(row).get.value.get 26 | print((1 to queensPosition-1).toList.foldLeft("")((s,_) => s + ".")) 27 | print("Q") 28 | print((queensPosition+1 to size).toList.foldLeft("")((s,_) => s + ".")) 29 | print(" [" + queensPosition + "]") 30 | println() 31 | }) 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /csp-queens/src/main/scala/com/mgu/csp/queens/QueensCSP.scala: -------------------------------------------------------------------------------- 1 | package com.mgu.csp.queens 2 | 3 | import com.mgu.csp.Variable.Identity 4 | import com.mgu.csp.queens.QueensCSP.id 5 | import com.mgu.csp.{AllDiff, Assignment, CSP, Constraint, NonMatchingDifference, Variable} 6 | 7 | class QueensCSP(size: Int = 8) extends CSP[Int] { 8 | 9 | override def constraints(): List[Constraint[Int]] = 10 | columnConstraints union diagonalConstraints((rowX, rowY) => rowX-rowY) union diagonalConstraints((rowX, rowY) => rowY-rowX) 11 | 12 | private def columnConstraints(): List[Constraint[Int]] = 13 | (for { 14 | rowX <- 1 to size 15 | rowY <- 1 to size 16 | } yield(rowX, rowY)) 17 | .filter { case (rowX, rowY) => rowX != rowY } 18 | .map { case (rowX, rowY) => AllDiff[Int](List(id(rowX), id(rowY))) } 19 | .toList 20 | 21 | private def diagonalConstraints(f: (Int, Int) => Int): List[Constraint[Int]] = 22 | (for { 23 | rowX <- 1 to size 24 | rowY <- 1 to size 25 | } yield (rowX, rowY)) 26 | .filter { case (rowX, rowY) => rowX != rowY } 27 | .map { case (rowX, rowY) => NonMatchingDifference(List(id(rowX), id(rowY)), f(rowX, rowY)) } 28 | .toList 29 | 30 | override def initialAssignment(): Assignment[Int] = { 31 | val unassignedVariables: Map[Identity, Variable[Int]] = (for { 32 | row <- 1 to size 33 | } yield id(row)) 34 | .map(identity => identity -> Variable[Int](identity)(None, domain = (1 to size).toList)) 35 | .toMap 36 | Assignment[Int](unassignedVariables) 37 | } 38 | } 39 | 40 | object QueensCSP { 41 | def id(row: Int): Identity = 42 | String.format("R%s", String.valueOf(row)) 43 | } -------------------------------------------------------------------------------- /csp-sudoku/src/main/scala/com/mgu/csp/sudoku/IdGenerator.scala: -------------------------------------------------------------------------------- 1 | package com.mgu.csp.sudoku 2 | 3 | import com.mgu.csp.Variable.Identity 4 | 5 | object IdGenerator { 6 | def id(row: Int, col: Int): Identity = { 7 | String.format("C%s%s", String.valueOf(row), String.valueOf(col)) 8 | } 9 | } -------------------------------------------------------------------------------- /csp-sudoku/src/main/scala/com/mgu/csp/sudoku/SudokuApp.scala: -------------------------------------------------------------------------------- 1 | package com.mgu.csp.sudoku 2 | 3 | import com.mgu.csp.sudoku.IdGenerator.id 4 | import com.mgu.csp.{DFSSolver, MinimumRemainingValue} 5 | 6 | object SudokuApp { 7 | 8 | val SUDOKU_PUZZLE: String = 9 | "003020600\n" + 10 | "900305001\n" + 11 | "001806400\n" + 12 | "008102900\n" + 13 | "700000008\n" + 14 | "006708200\n" + 15 | "002609500\n" + 16 | "800203009\n" + 17 | "005010300" 18 | 19 | def main(args: Array[String]): Unit = { 20 | val sudokuCSP = new SudokuCSP(SUDOKU_PUZZLE) 21 | val solver = new DFSSolver[Int](variableOrdering = new MinimumRemainingValue[Int]) 22 | val start = System.nanoTime() 23 | val assignment = solver.solve(sudokuCSP) 24 | val duration = (System.nanoTime() - start) / 1000000 25 | 26 | if (!assignment.isDefined) { 27 | println("Found no solution.") 28 | } else { 29 | println("Took " + duration + " ms.") 30 | val rows = for { 31 | row <- 1 to 9 32 | } yield (1 to 9).map(id(row, _)) 33 | 34 | rows.foreach(row => { 35 | println() 36 | row.foreach(col => print(assignment.get.variableAssignments.get(col).get.value.get)) 37 | }) 38 | 39 | println() 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /csp-sudoku/src/main/scala/com/mgu/csp/sudoku/SudokuCSP.scala: -------------------------------------------------------------------------------- 1 | package com.mgu.csp.sudoku 2 | 3 | import com.mgu.csp.Variable.Identity 4 | import com.mgu.csp.sudoku.IdGenerator.id 5 | import com.mgu.csp.{AllDiff, Assignment, CSP, Constraint, Variable} 6 | 7 | class SudokuCSP(val sudokuPuzzle: String) extends CSP[Int] { 8 | 9 | override def constraints(): List[Constraint[Int]] = 10 | constraintsOnRows union constraintsOnColumns union constraintsOnGrids 11 | 12 | private def constraintsOnRows(): List[Constraint[Int]] = { 13 | (for { row <- 1 to 9 } yield (1 to 9).map(col => id(row, col))) 14 | .map(dependentVariables => AllDiff[Int](dependentVariables.toList)) 15 | .toList 16 | } 17 | 18 | private def constraintsOnColumns(): List[Constraint[Int]] = { 19 | (for { column <- 1 to 9 } yield (1 to 9).map(row => id(row, column))) 20 | .map(dependentVariables => AllDiff[Int](dependentVariables.toList)) 21 | .toList 22 | } 23 | 24 | private def constraintsOnGrids(): List[Constraint[Int]] = { 25 | (for { 26 | gridRow <- 0 to 2 27 | gridCol <- 0 to 2 28 | } yield for { 29 | row <- 1 + gridRow * 3 to 1 + gridRow * 3 + 2 30 | col <- 1 + gridCol * 3 to 1 + gridCol * 3 + 2 31 | } yield id(row, col)) 32 | .map(dependentVariables => AllDiff[Int](dependentVariables.toList)) 33 | .toList 34 | } 35 | 36 | override def initialAssignment(): Assignment[Int] = { 37 | 38 | val unassignedVariables: Map[Identity, Variable[Int]] = (for { 39 | row <- 1 to 9 40 | col <- 1 to 9 41 | } yield (row, col)) 42 | .map { case (row, col) => id(row, col) } 43 | .map(identity => identity -> Variable[Int](identity)(None, domain = (1 to 9).toList)) 44 | .toMap 45 | 46 | lazy val lazyConstraints: List[Constraint[Int]] = constraints() 47 | val emptyAssignment = Assignment[Int](unassignedVariables) 48 | val lines = sudokuPuzzle.split("\n") 49 | 50 | (for { 51 | line <- 0 to 8 52 | charAt <- 0 to 8 53 | } yield (line, charAt)) 54 | .map { case (line, charAt) => (id(line+1, charAt+1), lines(line).charAt(charAt).asDigit) } 55 | .filterNot { case (id, number) => number == 0 } 56 | .foldLeft(emptyAssignment)((assignment, idAndValue) => assignment.assign(idAndValue._1, idAndValue._2, lazyConstraints)) 57 | } 58 | } -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.8 -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn --------------------------------------------------------------------------------