From 6fd2aea9d2c492126865944df07c85dd72358ee6 Mon Sep 17 00:00:00 2001 From: Matthew Butterick Date: Tue, 30 Sep 2014 15:03:25 -0700 Subject: [PATCH] tribulations --- csp/aima/csp.py | 3 +- csp/constraint.rkt | 167 ++ csp/csp.rkt | 5 +- .../API Documentation.webloc | 8 + csp/python-constraint/trials/abc.py | 30 + csp/python-constraint/trials/coins.py | 30 + csp/python-constraint/trials/constraint.py | 1434 +++++++++++++++++ csp/python-constraint/trials/crosswords.py | 153 ++ csp/python-constraint/trials/einstein.py | 201 +++ csp/python-constraint/trials/einstein2.py | 190 +++ csp/python-constraint/trials/large.mask | 27 + csp/python-constraint/trials/medium.mask | 19 + csp/python-constraint/trials/python.mask | 8 + csp/python-constraint/trials/queens.py | 47 + csp/python-constraint/trials/rooks.py | 49 + csp/python-constraint/trials/seisseisdoze.py | 32 + csp/python-constraint/trials/sendmoremoney.py | 34 + csp/python-constraint/trials/small.mask | 8 + csp/python-constraint/trials/studentdesks.py | 39 + csp/python-constraint/trials/sudoku.py | 61 + csp/python-constraint/trials/twotwofour.py | 28 + csp/python-constraint/trials/xsum.py | 37 + 22 files changed, 2607 insertions(+), 3 deletions(-) create mode 100644 csp/constraint.rkt create mode 100644 csp/python-constraint/API Documentation.webloc create mode 100755 csp/python-constraint/trials/abc.py create mode 100755 csp/python-constraint/trials/coins.py create mode 100644 csp/python-constraint/trials/constraint.py create mode 100755 csp/python-constraint/trials/crosswords.py create mode 100755 csp/python-constraint/trials/einstein.py create mode 100755 csp/python-constraint/trials/einstein2.py create mode 100644 csp/python-constraint/trials/large.mask create mode 100644 csp/python-constraint/trials/medium.mask create mode 100644 csp/python-constraint/trials/python.mask create mode 100755 csp/python-constraint/trials/queens.py create mode 100755 csp/python-constraint/trials/rooks.py create mode 100755 csp/python-constraint/trials/seisseisdoze.py create mode 100755 csp/python-constraint/trials/sendmoremoney.py create mode 100644 csp/python-constraint/trials/small.mask create mode 100755 csp/python-constraint/trials/studentdesks.py create mode 100644 csp/python-constraint/trials/sudoku.py create mode 100755 csp/python-constraint/trials/twotwofour.py create mode 100755 csp/python-constraint/trials/xsum.py diff --git a/csp/aima/csp.py b/csp/aima/csp.py index 9347599a..935b2035 100644 --- a/csp/aima/csp.py +++ b/csp/aima/csp.py @@ -227,6 +227,7 @@ def min_conflicts(csp, max_steps=1000000): csp.assign(var, val, current) # Now repeapedly choose a random conflicted variable and change it for i in range(max_steps): + print i conflicted = csp.conflicted_vars(current) if not conflicted: return current @@ -447,4 +448,4 @@ def solve_zebra(algorithm=min_conflicts, **args): print return ans['Zebra'], ans['Water'], z.nassigns, ans, - +solve_zebra() diff --git a/csp/constraint.rkt b/csp/constraint.rkt new file mode 100644 index 00000000..7c0ed101 --- /dev/null +++ b/csp/constraint.rkt @@ -0,0 +1,167 @@ +#lang racket/base +(require racket/class racket/contract racket/match) +(require sugar/container sugar/debug) + +(module+ test (require rackunit)) + +;; Adapted from work by Gustavo Niemeyer +#| +# Copyright (c) 2005-2014 - Gustavo Niemeyer +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +|# + +(provide (all-defined-out)) +;(provide Problem Variable Domain Unassigned Solver BacktrackingSolver RecursiveBacktrackingSolver MinConflictsSolver Constraint FunctionConstraint AllDifferentConstraint AllEqualConstraint MaxSumConstraint ExactSumConstraint MinSumConstraint InSetConstraint NotInSetConstraint SomeInSetConstraint SomeNotInSetConstraint) + +;(define Problem/c (λ(x) (is-a x Problem))) + +(define/contract Problem + ;; Class used to define a problem and retrieve solutions + + (class/c [reset (->m void?)] + ;; todo: tighten `object?` contracts + [setSolver (object? . ->m . void?)] + [getSolver (->m object?)] + ;; todo: tighten `object?` contract + [addVariable (any/c (or/c list? object?) . ->m . void?)] + [getSolutions (->m list?)]) + (class object% + (super-new) + + (init-field [solver #f]) + (field [_solver (or solver (new BacktrackingSolver))] + [_constraints null] + [_variables (make-hash)]) + + (define/public (reset) + ;; Reset the current problem definition + (set! _constraints null) + (hash-clear! _variables)) + + (define/public (setSolver solver) + ;; Change the problem solver currently in use + (set! _solver solver)) + + (define/public (getSolver) + ;; Obtain the problem solver currently in use + _solver) + + (define/public (addVariable variable domain) + ;; Add a variable to the problem + (when (variable . in? . _variables) + (error 'addVariable (format "Tried to insert duplicated variable ~a" variable))) + (cond + [(list? domain) (report domain) (set! domain (new Domain [set domain]))] + ;; todo: test for `instance-of-Domain?` ; how to copy domain? + [(object? domain) (report 'foo) (report domain) (set! domain '(copy.copy domain))] + [else (error 'addVariable "Domains must be instances of subclasses of Domain")]) + (when (not domain) ; todo: check this test + (error 'addVariable "Domain is empty")) + (hash-set! _variables variable (get-field _list domain))) + + (define/public (addVariables variables domain) + ;; Add one or more variables to the problem + (for-each (λ(var) (addVariable var domain)) variables)) + + (define/public (getSolutions) + ;; Find and return all solutions to the problem + (define-values (domains constraints vconstraints) (_getArgs)) + (if (not domains) + null + (send _solver getSolutions domains constraints vconstraints))) + + (define/public (_getArgs) + (define domains (hash-copy _variables)) + (define allvariables (hash-keys domains)) + (define constraints null) + (for ([constraint-variables-pair (in-list _constraints)]) + (match-define (cons constraint variables) constraint-variables-pair) + (when (not variables) + (set! variables allvariables)) + (set! constraints (append constraints (list (cons constraint variables))))) + (define vconstraints (make-hash)) + (for ([variable (in-hash-keys domains)]) + (hash-set! vconstraints variable null)) + (for ([constraint-variables-pair (in-list constraints)]) + (match-define (cons constraint variables) constraint-variables-pair) + (for ([variable (in-list variables)]) + (hash-update! vconstraints variable (λ(val) (append val (list (cons constraint variables))))))) + (for ([constraint-variables-pair (in-list constraints)]) + (match-define (cons constraint variables) constraint-variables-pair) + (send constraint preProcess variables domains constraints vconstraints)) + (define result #f) + (let/ec done + (for ([domain (in-list (hash-values domains))]) + (send domain resetState) + (when (not domain) + (set! result (values null null null)) + (done))) + (set! result (values domains constraints vconstraints))) + result) + + + )) + +(module+ test + (check-equal? (get-field _solver (new Problem [solver 'solver-in])) 'solver-in) + (check-equal? (get-field _constraints (new Problem)) null) + (check-equal? (get-field _variables (new Problem)) (make-hash)) + + (define problem (new Problem)) + (send problem addVariable "a" '(1 2)) + (check-equal? (hash-ref (get-field _variables problem) "a") '(1 2)) + (send problem reset) + (check-equal? (get-field _variables problem) (make-hash)) + (send problem addVariables '("a" "b") '(1 2 3)) + (check-equal? (hash-ref (get-field _variables problem) "a") '(1 2 3)) + (check-equal? (hash-ref (get-field _variables problem) "b") '(1 2 3)) + (get-field _variables problem) + (send problem getSolutions) + ) + + +(define BacktrackingSolver + (class object% + (super-new))) + + +;; ---------------------------------------------------------------------- +;; Domains +;; ---------------------------------------------------------------------- + + +(define Domain + ;; Class used to control possible values for variables + ;; When list or tuples are used as domains, they are automatically + ;; converted to an instance of that class. + + (class object% + (super-new) + (init-field set) + (field [_list set]))) + +(module+ main + (define p (new Problem)) + (define d (new Domain [set '(1 2)])) + ) \ No newline at end of file diff --git a/csp/csp.rkt b/csp/csp.rkt index f101ebb6..e0cb48bc 100644 --- a/csp/csp.rkt +++ b/csp/csp.rkt @@ -4,6 +4,7 @@ ;; http://aima-python.googlecode.com/svn/trunk/csp.py (require racket/list racket/bool racket/contract racket/class racket/match racket/generator racket/string) +(require sugar/debug) (require "utils.rkt" "search.rkt") (module+ test (require rackunit)) @@ -310,9 +311,9 @@ Set up to do recursive backtracking search. Allow the following options: [A (in-list type)] [B (in-list type)]) (when (not (equal? A B)) - (when (not (memq B (hash-ref neighbors A))) + (when (not (member B (report (hash-ref neighbors A)))) (hash-update! neighbors A (λ(v) (append v B)))) - (when (not (memq A (hash-ref neighbors B))) + (when (not (member A (hash-ref neighbors B))) (hash-update! neighbors B (λ(v) (append v A)))))) (define (zebra_constraint A a B b [recurse 0]) diff --git a/csp/python-constraint/API Documentation.webloc b/csp/python-constraint/API Documentation.webloc new file mode 100644 index 00000000..3cae8191 --- /dev/null +++ b/csp/python-constraint/API Documentation.webloc @@ -0,0 +1,8 @@ + + + + + URL + http://labix.org/doc/constraint/ + + diff --git a/csp/python-constraint/trials/abc.py b/csp/python-constraint/trials/abc.py new file mode 100755 index 00000000..800cf2c0 --- /dev/null +++ b/csp/python-constraint/trials/abc.py @@ -0,0 +1,30 @@ +#!/usr/bin/python +# +# What's the minimum value for: +# +# ABC +# ------- +# A+B+C +# +# From http://www.umassd.edu/mathcontest/abc.cfm +# +from constraint import * + +def main(): + problem = Problem() + problem.addVariables("abc", range(1,10)) + problem.getSolutions() + minvalue = 999/(9*3) + minsolution = {} + for solution in problem.getSolutions(): + a = solution["a"] + b = solution["b"] + c = solution["c"] + value = (a*100+b*10+c)/(a+b+c) + if value < minvalue: + minsolution = solution + print (minsolution["a"]*100+minsolution["b"]*10+minsolution["c"])/(minsolution["a"]+minsolution["b"]+minsolution["c"]) + print minsolution + +if __name__ == "__main__": + main() diff --git a/csp/python-constraint/trials/coins.py b/csp/python-constraint/trials/coins.py new file mode 100755 index 00000000..cb47537d --- /dev/null +++ b/csp/python-constraint/trials/coins.py @@ -0,0 +1,30 @@ +#!/usr/bin/python +# +# 100 coins must sum to $5.00 +# +# That's kind of a country-specific problem, since depending on the +# country there are different values for coins. Here is presented +# the solution for a given set. +# +from constraint import * +import sys + +def main(): + problem = Problem() + total = 5.00 + variables = ("0.01", "0.05", "0.10", "0.25") + values = [float(x) for x in variables] + for variable, value in zip(variables, values): + problem.addVariable(variable, range(int(total/value))) + problem.addConstraint(ExactSumConstraint(total, values), variables) + problem.addConstraint(ExactSumConstraint(100)) + solutions = problem.getSolutionIter() + for i, solution in enumerate(solutions): + sys.stdout.write("%03d -> " % (i+1)) + for variable in variables: + sys.stdout.write("%s:%d " % (variable, solution[variable])) + sys.stdout.write("\n") + +if __name__ == "__main__": + main() + diff --git a/csp/python-constraint/trials/constraint.py b/csp/python-constraint/trials/constraint.py new file mode 100644 index 00000000..b1cd836b --- /dev/null +++ b/csp/python-constraint/trials/constraint.py @@ -0,0 +1,1434 @@ +#!/usr/bin/python +# +# Copyright (c) 2005-2014 - Gustavo Niemeyer +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" +@var Unassigned: Helper object instance representing unassigned values + +@sort: Problem, Variable, Domain +@group Solvers: Solver, + BacktrackingSolver, + RecursiveBacktrackingSolver, + MinConflictsSolver +@group Constraints: Constraint, + FunctionConstraint, + AllDifferentConstraint, + AllEqualConstraint, + MaxSumConstraint, + ExactSumConstraint, + MinSumConstraint, + InSetConstraint, + NotInSetConstraint, + SomeInSetConstraint, + SomeNotInSetConstraint +""" +import random +import copy + +__all__ = ["Problem", "Variable", "Domain", "Unassigned", + "Solver", "BacktrackingSolver", "RecursiveBacktrackingSolver", + "MinConflictsSolver", "Constraint", "FunctionConstraint", + "AllDifferentConstraint", "AllEqualConstraint", "MaxSumConstraint", + "ExactSumConstraint", "MinSumConstraint", "InSetConstraint", + "NotInSetConstraint", "SomeInSetConstraint", + "SomeNotInSetConstraint"] + +class Problem(object): + """ + Class used to define a problem and retrieve solutions + """ + + def __init__(self, solver=None): + """ + @param solver: Problem solver used to find solutions + (default is L{BacktrackingSolver}) + @type solver: instance of a L{Solver} subclass + """ + self._solver = solver or BacktrackingSolver() + self._constraints = [] + self._variables = {} + + def reset(self): + """ + Reset the current problem definition + + Example: + + >>> problem = Problem() + >>> problem.addVariable("a", [1, 2]) + >>> problem.reset() + >>> problem.getSolution() + >>> + """ + del self._constraints[:] + self._variables.clear() + + def setSolver(self, solver): + """ + Change the problem solver currently in use + + Example: + + >>> solver = BacktrackingSolver() + >>> problem = Problem(solver) + >>> problem.getSolver() is solver + True + + @param solver: New problem solver + @type solver: instance of a C{Solver} subclass + """ + self._solver = solver + + def getSolver(self): + """ + Obtain the problem solver currently in use + + Example: + + >>> solver = BacktrackingSolver() + >>> problem = Problem(solver) + >>> problem.getSolver() is solver + True + + @return: Solver currently in use + @rtype: instance of a L{Solver} subclass + """ + return self._solver + + def addVariable(self, variable, domain): + """ + Add a variable to the problem + + Example: + + >>> problem = Problem() + >>> problem.addVariable("a", [1, 2]) + >>> problem.getSolution() in ({'a': 1}, {'a': 2}) + True + + @param variable: Object representing a problem variable + @type variable: hashable object + @param domain: Set of items defining the possible values that + the given variable may assume + @type domain: list, tuple, or instance of C{Domain} + """ + if variable in self._variables: + raise ValueError, "Tried to insert duplicated variable %s" % \ + repr(variable) + if type(domain) in (list, tuple): + domain = Domain(domain) + elif isinstance(domain, Domain): + domain = copy.copy(domain) + else: + raise TypeError, "Domains must be instances of subclasses of "\ + "the Domain class" + if not domain: + raise ValueError, "Domain is empty" + self._variables[variable] = domain + + def addVariables(self, variables, domain): + """ + Add one or more variables to the problem + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2, 3]) + >>> solutions = problem.getSolutions() + >>> len(solutions) + 9 + >>> {'a': 3, 'b': 1} in solutions + True + + @param variables: Any object containing a sequence of objects + represeting problem variables + @type variables: sequence of hashable objects + @param domain: Set of items defining the possible values that + the given variables may assume + @type domain: list, tuple, or instance of C{Domain} + """ + for variable in variables: + self.addVariable(variable, domain) + + def addConstraint(self, constraint, variables=None): + """ + Add a constraint to the problem + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2, 3]) + >>> problem.addConstraint(lambda a, b: b == a+1, ["a", "b"]) + >>> solutions = problem.getSolutions() + >>> + + @param constraint: Constraint to be included in the problem + @type constraint: instance a L{Constraint} subclass or a + function to be wrapped by L{FunctionConstraint} + @param variables: Variables affected by the constraint (default to + all variables). Depending on the constraint type + the order may be important. + @type variables: set or sequence of variables + """ + if not isinstance(constraint, Constraint): + if callable(constraint): + constraint = FunctionConstraint(constraint) + else: + raise ValueError, "Constraints must be instances of "\ + "subclasses of the Constraint class" + self._constraints.append((constraint, variables)) + + def getSolution(self): + """ + Find and return a solution to the problem + + Example: + + >>> problem = Problem() + >>> problem.getSolution() is None + True + >>> problem.addVariables(["a"], [42]) + >>> problem.getSolution() + {'a': 42} + + @return: Solution for the problem + @rtype: dictionary mapping variables to values + """ + domains, constraints, vconstraints = self._getArgs() + if not domains: + return None + return self._solver.getSolution(domains, constraints, vconstraints) + + def getSolutions(self): + """ + Find and return all solutions to the problem + + Example: + + >>> problem = Problem() + >>> problem.getSolutions() == [] + True + >>> problem.addVariables(["a"], [42]) + >>> problem.getSolutions() + [{'a': 42}] + + @return: All solutions for the problem + @rtype: list of dictionaries mapping variables to values + """ + domains, constraints, vconstraints = self._getArgs() + if not domains: + return [] + return self._solver.getSolutions(domains, constraints, vconstraints) + + def getSolutionIter(self): + """ + Return an iterator to the solutions of the problem + + Example: + + >>> problem = Problem() + >>> list(problem.getSolutionIter()) == [] + True + >>> problem.addVariables(["a"], [42]) + >>> iter = problem.getSolutionIter() + >>> iter.next() + {'a': 42} + >>> iter.next() + Traceback (most recent call last): + File "", line 1, in ? + StopIteration + """ + domains, constraints, vconstraints = self._getArgs() + if not domains: + return iter(()) + return self._solver.getSolutionIter(domains, constraints, + vconstraints) + + def _getArgs(self): + domains = self._variables.copy() + allvariables = domains.keys() + constraints = [] + for constraint, variables in self._constraints: + if not variables: + variables = allvariables + constraints.append((constraint, variables)) + vconstraints = {} + for variable in domains: + vconstraints[variable] = [] + for constraint, variables in constraints: + for variable in variables: + vconstraints[variable].append((constraint, variables)) + for constraint, variables in constraints[:]: + constraint.preProcess(variables, domains, + constraints, vconstraints) + for domain in domains.values(): + domain.resetState() + if not domain: + return None, None, None + #doArc8(getArcs(domains, constraints), domains, {}) + return domains, constraints, vconstraints + +# ---------------------------------------------------------------------- +# Solvers +# ---------------------------------------------------------------------- + +def getArcs(domains, constraints): + """ + Return a dictionary mapping pairs (arcs) of constrained variables + + @attention: Currently unused. + """ + arcs = {} + for x in constraints: + constraint, variables = x + if len(variables) == 2: + variable1, variable2 = variables + arcs.setdefault(variable1, {})\ + .setdefault(variable2, [])\ + .append(x) + arcs.setdefault(variable2, {})\ + .setdefault(variable1, [])\ + .append(x) + return arcs + +def doArc8(arcs, domains, assignments): + """ + Perform the ARC-8 arc checking algorithm and prune domains + + @attention: Currently unused. + """ + check = dict.fromkeys(domains, True) + while check: + variable, _ = check.popitem() + if variable not in arcs or variable in assignments: + continue + domain = domains[variable] + arcsvariable = arcs[variable] + for othervariable in arcsvariable: + arcconstraints = arcsvariable[othervariable] + if othervariable in assignments: + otherdomain = [assignments[othervariable]] + else: + otherdomain = domains[othervariable] + if domain: + changed = False + for value in domain[:]: + assignments[variable] = value + if otherdomain: + for othervalue in otherdomain: + assignments[othervariable] = othervalue + for constraint, variables in arcconstraints: + if not constraint(variables, domains, + assignments, True): + break + else: + # All constraints passed. Value is safe. + break + else: + # All othervalues failed. Kill value. + domain.hideValue(value) + changed = True + del assignments[othervariable] + del assignments[variable] + #if changed: + # check.update(dict.fromkeys(arcsvariable)) + if not domain: + return False + return True + +class Solver(object): + """ + Abstract base class for solvers + + @sort: getSolution, getSolutions, getSolutionIter + """ + + def getSolution(self, domains, constraints, vconstraints): + """ + Return one solution for the given problem + + @param domains: Dictionary mapping variables to their domains + @type domains: dict + @param constraints: List of pairs of (constraint, variables) + @type constraints: list + @param vconstraints: Dictionary mapping variables to a list of + constraints affecting the given variables. + @type vconstraints: dict + """ + raise NotImplementedError, \ + "%s is an abstract class" % self.__class__.__name__ + + def getSolutions(self, domains, constraints, vconstraints): + """ + Return all solutions for the given problem + + @param domains: Dictionary mapping variables to domains + @type domains: dict + @param constraints: List of pairs of (constraint, variables) + @type constraints: list + @param vconstraints: Dictionary mapping variables to a list of + constraints affecting the given variables. + @type vconstraints: dict + """ + raise NotImplementedError, \ + "%s provides only a single solution" % self.__class__.__name__ + + def getSolutionIter(self, domains, constraints, vconstraints): + """ + Return an iterator for the solutions of the given problem + + @param domains: Dictionary mapping variables to domains + @type domains: dict + @param constraints: List of pairs of (constraint, variables) + @type constraints: list + @param vconstraints: Dictionary mapping variables to a list of + constraints affecting the given variables. + @type vconstraints: dict + """ + raise NotImplementedError, \ + "%s doesn't provide iteration" % self.__class__.__name__ + +class BacktrackingSolver(Solver): + """ + Problem solver with backtracking capabilities + + Examples: + + >>> result = [[('a', 1), ('b', 2)], + ... [('a', 1), ('b', 3)], + ... [('a', 2), ('b', 3)]] + + >>> problem = Problem(BacktrackingSolver()) + >>> problem.addVariables(["a", "b"], [1, 2, 3]) + >>> problem.addConstraint(lambda a, b: b > a, ["a", "b"]) + + >>> solution = problem.getSolution() + >>> sorted(solution.items()) in result + True + + >>> for solution in problem.getSolutionIter(): + ... sorted(solution.items()) in result + True + True + True + + >>> for solution in problem.getSolutions(): + ... sorted(solution.items()) in result + True + True + True + """#""" + + def __init__(self, forwardcheck=True): + """ + @param forwardcheck: If false forward checking will not be requested + to constraints while looking for solutions + (default is true) + @type forwardcheck: bool + """ + self._forwardcheck = forwardcheck + + def getSolutionIter(self, domains, constraints, vconstraints): + forwardcheck = self._forwardcheck + assignments = {} + + queue = [] + + while True: + + # Mix the Degree and Minimum Remaing Values (MRV) heuristics + lst = [(-len(vconstraints[variable]), + len(domains[variable]), variable) for variable in domains] + lst.sort() + for item in lst: + if item[-1] not in assignments: + # Found unassigned variable + variable = item[-1] + values = domains[variable][:] + if forwardcheck: + pushdomains = [domains[x] for x in domains + if x not in assignments and + x != variable] + else: + pushdomains = None + break + else: + # No unassigned variables. We've got a solution. Go back + # to last variable, if there's one. + yield assignments.copy() + if not queue: + return + variable, values, pushdomains = queue.pop() + if pushdomains: + for domain in pushdomains: + domain.popState() + + while True: + # We have a variable. Do we have any values left? + if not values: + # No. Go back to last variable, if there's one. + del assignments[variable] + while queue: + variable, values, pushdomains = queue.pop() + if pushdomains: + for domain in pushdomains: + domain.popState() + if values: + break + del assignments[variable] + else: + return + + # Got a value. Check it. + assignments[variable] = values.pop() + + if pushdomains: + for domain in pushdomains: + domain.pushState() + + for constraint, variables in vconstraints[variable]: + if not constraint(variables, domains, assignments, + pushdomains): + # Value is not good. + break + else: + break + + if pushdomains: + for domain in pushdomains: + domain.popState() + + # Push state before looking for next variable. + queue.append((variable, values, pushdomains)) + + raise RuntimeError, "Can't happen" + + def getSolution(self, domains, constraints, vconstraints): + iter = self.getSolutionIter(domains, constraints, vconstraints) + try: + return iter.next() + except StopIteration: + return None + + def getSolutions(self, domains, constraints, vconstraints): + return list(self.getSolutionIter(domains, constraints, vconstraints)) + + +class RecursiveBacktrackingSolver(Solver): + """ + Recursive problem solver with backtracking capabilities + + Examples: + + >>> result = [[('a', 1), ('b', 2)], + ... [('a', 1), ('b', 3)], + ... [('a', 2), ('b', 3)]] + + >>> problem = Problem(RecursiveBacktrackingSolver()) + >>> problem.addVariables(["a", "b"], [1, 2, 3]) + >>> problem.addConstraint(lambda a, b: b > a, ["a", "b"]) + + >>> solution = problem.getSolution() + >>> sorted(solution.items()) in result + True + + >>> for solution in problem.getSolutions(): + ... sorted(solution.items()) in result + True + True + True + + >>> problem.getSolutionIter() + Traceback (most recent call last): + ... + NotImplementedError: RecursiveBacktrackingSolver doesn't provide iteration + """#""" + + def __init__(self, forwardcheck=True): + """ + @param forwardcheck: If false forward checking will not be requested + to constraints while looking for solutions + (default is true) + @type forwardcheck: bool + """ + self._forwardcheck = forwardcheck + + def recursiveBacktracking(self, solutions, domains, vconstraints, + assignments, single): + + # Mix the Degree and Minimum Remaing Values (MRV) heuristics + lst = [(-len(vconstraints[variable]), + len(domains[variable]), variable) for variable in domains] + lst.sort() + for item in lst: + if item[-1] not in assignments: + # Found an unassigned variable. Let's go. + break + else: + # No unassigned variables. We've got a solution. + solutions.append(assignments.copy()) + return solutions + + variable = item[-1] + assignments[variable] = None + + forwardcheck = self._forwardcheck + if forwardcheck: + pushdomains = [domains[x] for x in domains if x not in assignments] + else: + pushdomains = None + + for value in domains[variable]: + assignments[variable] = value + if pushdomains: + for domain in pushdomains: + domain.pushState() + for constraint, variables in vconstraints[variable]: + if not constraint(variables, domains, assignments, + pushdomains): + # Value is not good. + break + else: + # Value is good. Recurse and get next variable. + self.recursiveBacktracking(solutions, domains, vconstraints, + assignments, single) + if solutions and single: + return solutions + if pushdomains: + for domain in pushdomains: + domain.popState() + del assignments[variable] + return solutions + + def getSolution(self, domains, constraints, vconstraints): + solutions = self.recursiveBacktracking([], domains, vconstraints, + {}, True) + return solutions and solutions[0] or None + + def getSolutions(self, domains, constraints, vconstraints): + return self.recursiveBacktracking([], domains, vconstraints, + {}, False) + + +class MinConflictsSolver(Solver): + """ + Problem solver based on the minimum conflicts theory + + Examples: + + >>> result = [[('a', 1), ('b', 2)], + ... [('a', 1), ('b', 3)], + ... [('a', 2), ('b', 3)]] + + >>> problem = Problem(MinConflictsSolver()) + >>> problem.addVariables(["a", "b"], [1, 2, 3]) + >>> problem.addConstraint(lambda a, b: b > a, ["a", "b"]) + + >>> solution = problem.getSolution() + >>> sorted(solution.items()) in result + True + + >>> problem.getSolutions() + Traceback (most recent call last): + ... + NotImplementedError: MinConflictsSolver provides only a single solution + + >>> problem.getSolutionIter() + Traceback (most recent call last): + ... + NotImplementedError: MinConflictsSolver doesn't provide iteration + """#""" + + def __init__(self, steps=1000): + """ + @param steps: Maximum number of steps to perform before giving up + when looking for a solution (default is 1000) + @type steps: int + """ + self._steps = steps + + def getSolution(self, domains, constraints, vconstraints): + assignments = {} + # Initial assignment + for variable in domains: + assignments[variable] = random.choice(domains[variable]) + for _ in xrange(self._steps): + conflicted = False + lst = domains.keys() + random.shuffle(lst) + for variable in lst: + # Check if variable is not in conflict + for constraint, variables in vconstraints[variable]: + if not constraint(variables, domains, assignments): + break + else: + continue + # Variable has conflicts. Find values with less conflicts. + mincount = len(vconstraints[variable]) + minvalues = [] + for value in domains[variable]: + assignments[variable] = value + count = 0 + for constraint, variables in vconstraints[variable]: + if not constraint(variables, domains, assignments): + count += 1 + if count == mincount: + minvalues.append(value) + elif count < mincount: + mincount = count + del minvalues[:] + minvalues.append(value) + # Pick a random one from these values. + assignments[variable] = random.choice(minvalues) + conflicted = True + if not conflicted: + return assignments + return None + +# ---------------------------------------------------------------------- +# Variables +# ---------------------------------------------------------------------- + +class Variable(object): + """ + Helper class for variable definition + + Using this class is optional, since any hashable object, + including plain strings and integers, may be used as variables. + """ + + def __init__(self, name): + """ + @param name: Generic variable name for problem-specific purposes + @type name: string + """ + self.name = name + + def __repr__(self): + return self.name + +Unassigned = Variable("Unassigned") + +# ---------------------------------------------------------------------- +# Domains +# ---------------------------------------------------------------------- + +class Domain(list): + """ + Class used to control possible values for variables + + When list or tuples are used as domains, they are automatically + converted to an instance of that class. + """ + + def __init__(self, set): + """ + @param set: Set of values that the given variables may assume + @type set: set of objects comparable by equality + """ + list.__init__(self, set) + self._hidden = [] + self._states = [] + + def resetState(self): + """ + Reset to the original domain state, including all possible values + """ + self.extend(self._hidden) + del self._hidden[:] + del self._states[:] + + def pushState(self): + """ + Save current domain state + + Variables hidden after that call are restored when that state + is popped from the stack. + """ + self._states.append(len(self)) + + def popState(self): + """ + Restore domain state from the top of the stack + + Variables hidden since the last popped state are then available + again. + """ + diff = self._states.pop()-len(self) + if diff: + self.extend(self._hidden[-diff:]) + del self._hidden[-diff:] + + def hideValue(self, value): + """ + Hide the given value from the domain + + After that call the given value won't be seen as a possible value + on that domain anymore. The hidden value will be restored when the + previous saved state is popped. + + @param value: Object currently available in the domain + """ + list.remove(self, value) + self._hidden.append(value) + +# ---------------------------------------------------------------------- +# Constraints +# ---------------------------------------------------------------------- + +class Constraint(object): + """ + Abstract base class for constraints + """ + + def __call__(self, variables, domains, assignments, forwardcheck=False): + """ + Perform the constraint checking + + If the forwardcheck parameter is not false, besides telling if + the constraint is currently broken or not, the constraint + implementation may choose to hide values from the domains of + unassigned variables to prevent them from being used, and thus + prune the search space. + + @param variables: Variables affected by that constraint, in the + same order provided by the user + @type variables: sequence + @param domains: Dictionary mapping variables to their domains + @type domains: dict + @param assignments: Dictionary mapping assigned variables to their + current assumed value + @type assignments: dict + @param forwardcheck: Boolean value stating whether forward checking + should be performed or not + @return: Boolean value stating if this constraint is currently + broken or not + @rtype: bool + """#""" + return True + + def preProcess(self, variables, domains, constraints, vconstraints): + """ + Preprocess variable domains + + This method is called before starting to look for solutions, + and is used to prune domains with specific constraint logic + when possible. For instance, any constraints with a single + variable may be applied on all possible values and removed, + since they may act on individual values even without further + knowledge about other assignments. + + @param variables: Variables affected by that constraint, in the + same order provided by the user + @type variables: sequence + @param domains: Dictionary mapping variables to their domains + @type domains: dict + @param constraints: List of pairs of (constraint, variables) + @type constraints: list + @param vconstraints: Dictionary mapping variables to a list of + constraints affecting the given variables. + @type vconstraints: dict + """#""" + if len(variables) == 1: + variable = variables[0] + domain = domains[variable] + for value in domain[:]: + if not self(variables, domains, {variable: value}): + domain.remove(value) + constraints.remove((self, variables)) + vconstraints[variable].remove((self, variables)) + + def forwardCheck(self, variables, domains, assignments, + _unassigned=Unassigned): + """ + Helper method for generic forward checking + + Currently, this method acts only when there's a single + unassigned variable. + + @param variables: Variables affected by that constraint, in the + same order provided by the user + @type variables: sequence + @param domains: Dictionary mapping variables to their domains + @type domains: dict + @param assignments: Dictionary mapping assigned variables to their + current assumed value + @type assignments: dict + @return: Boolean value stating if this constraint is currently + broken or not + @rtype: bool + """#""" + unassignedvariable = _unassigned + for variable in variables: + if variable not in assignments: + if unassignedvariable is _unassigned: + unassignedvariable = variable + else: + break + else: + if unassignedvariable is not _unassigned: + # Remove from the unassigned variable domain's all + # values which break our variable's constraints. + domain = domains[unassignedvariable] + if domain: + for value in domain[:]: + assignments[unassignedvariable] = value + if not self(variables, domains, assignments): + domain.hideValue(value) + del assignments[unassignedvariable] + if not domain: + return False + return True + +class FunctionConstraint(Constraint): + """ + Constraint which wraps a function defining the constraint logic + + Examples: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> def func(a, b): + ... return b > a + >>> problem.addConstraint(func, ["a", "b"]) + >>> problem.getSolution() + {'a': 1, 'b': 2} + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> def func(a, b): + ... return b > a + >>> problem.addConstraint(FunctionConstraint(func), ["a", "b"]) + >>> problem.getSolution() + {'a': 1, 'b': 2} + """#""" + + def __init__(self, func, assigned=True): + """ + @param func: Function wrapped and queried for constraint logic + @type func: callable object + @param assigned: Whether the function may receive unassigned + variables or not + @type assigned: bool + """ + self._func = func + self._assigned = assigned + + def __call__(self, variables, domains, assignments, forwardcheck=False, + _unassigned=Unassigned): + parms = [assignments.get(x, _unassigned) for x in variables] + missing = parms.count(_unassigned) + if missing: + return ((self._assigned or self._func(*parms)) and + (not forwardcheck or missing != 1 or + self.forwardCheck(variables, domains, assignments))) + return self._func(*parms) + +class AllDifferentConstraint(Constraint): + """ + Constraint enforcing that values of all given variables are different + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> problem.addConstraint(AllDifferentConstraint()) + >>> sorted(sorted(x.items()) for x in problem.getSolutions()) + [[('a', 1), ('b', 2)], [('a', 2), ('b', 1)]] + """#""" + + def __call__(self, variables, domains, assignments, forwardcheck=False, + _unassigned=Unassigned): + seen = {} + for variable in variables: + value = assignments.get(variable, _unassigned) + if value is not _unassigned: + if value in seen: + return False + seen[value] = True + if forwardcheck: + for variable in variables: + if variable not in assignments: + domain = domains[variable] + for value in seen: + if value in domain: + domain.hideValue(value) + if not domain: + return False + return True + +class AllEqualConstraint(Constraint): + """ + Constraint enforcing that values of all given variables are equal + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> problem.addConstraint(AllEqualConstraint()) + >>> sorted(sorted(x.items()) for x in problem.getSolutions()) + [[('a', 1), ('b', 1)], [('a', 2), ('b', 2)]] + """#""" + + def __call__(self, variables, domains, assignments, forwardcheck=False, + _unassigned=Unassigned): + singlevalue = _unassigned + for variable in variables: + value = assignments.get(variable, _unassigned) + if singlevalue is _unassigned: + singlevalue = value + elif value is not _unassigned and value != singlevalue: + return False + if forwardcheck and singlevalue is not _unassigned: + for variable in variables: + if variable not in assignments: + domain = domains[variable] + if singlevalue not in domain: + return False + for value in domain[:]: + if value != singlevalue: + domain.hideValue(value) + return True + +class MaxSumConstraint(Constraint): + """ + Constraint enforcing that values of given variables sum up to + a given amount + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> problem.addConstraint(MaxSumConstraint(3)) + >>> sorted(sorted(x.items()) for x in problem.getSolutions()) + [[('a', 1), ('b', 1)], [('a', 1), ('b', 2)], [('a', 2), ('b', 1)]] + """#""" + + def __init__(self, maxsum, multipliers=None): + """ + @param maxsum: Value to be considered as the maximum sum + @type maxsum: number + @param multipliers: If given, variable values will be multiplied by + the given factors before being summed to be checked + @type multipliers: sequence of numbers + """ + self._maxsum = maxsum + self._multipliers = multipliers + + def preProcess(self, variables, domains, constraints, vconstraints): + Constraint.preProcess(self, variables, domains, + constraints, vconstraints) + multipliers = self._multipliers + maxsum = self._maxsum + if multipliers: + for variable, multiplier in zip(variables, multipliers): + domain = domains[variable] + for value in domain[:]: + if value*multiplier > maxsum: + domain.remove(value) + else: + for variable in variables: + domain = domains[variable] + for value in domain[:]: + if value > maxsum: + domain.remove(value) + + def __call__(self, variables, domains, assignments, forwardcheck=False): + multipliers = self._multipliers + maxsum = self._maxsum + sum = 0 + if multipliers: + for variable, multiplier in zip(variables, multipliers): + if variable in assignments: + sum += assignments[variable]*multiplier + if type(sum) is float: + sum = round(sum, 10) + if sum > maxsum: + return False + if forwardcheck: + for variable, multiplier in zip(variables, multipliers): + if variable not in assignments: + domain = domains[variable] + for value in domain[:]: + if sum+value*multiplier > maxsum: + domain.hideValue(value) + if not domain: + return False + else: + for variable in variables: + if variable in assignments: + sum += assignments[variable] + if type(sum) is float: + sum = round(sum, 10) + if sum > maxsum: + return False + if forwardcheck: + for variable in variables: + if variable not in assignments: + domain = domains[variable] + for value in domain[:]: + if sum+value > maxsum: + domain.hideValue(value) + if not domain: + return False + return True + +class ExactSumConstraint(Constraint): + """ + Constraint enforcing that values of given variables sum exactly + to a given amount + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> problem.addConstraint(ExactSumConstraint(3)) + >>> sorted(sorted(x.items()) for x in problem.getSolutions()) + [[('a', 1), ('b', 2)], [('a', 2), ('b', 1)]] + """#""" + + def __init__(self, exactsum, multipliers=None): + """ + @param exactsum: Value to be considered as the exact sum + @type exactsum: number + @param multipliers: If given, variable values will be multiplied by + the given factors before being summed to be checked + @type multipliers: sequence of numbers + """ + self._exactsum = exactsum + self._multipliers = multipliers + + def preProcess(self, variables, domains, constraints, vconstraints): + Constraint.preProcess(self, variables, domains, + constraints, vconstraints) + multipliers = self._multipliers + exactsum = self._exactsum + if multipliers: + for variable, multiplier in zip(variables, multipliers): + domain = domains[variable] + for value in domain[:]: + if value*multiplier > exactsum: + domain.remove(value) + else: + for variable in variables: + domain = domains[variable] + for value in domain[:]: + if value > exactsum: + domain.remove(value) + + def __call__(self, variables, domains, assignments, forwardcheck=False): + multipliers = self._multipliers + exactsum = self._exactsum + sum = 0 + missing = False + if multipliers: + for variable, multiplier in zip(variables, multipliers): + if variable in assignments: + sum += assignments[variable]*multiplier + else: + missing = True + if type(sum) is float: + sum = round(sum, 10) + if sum > exactsum: + return False + if forwardcheck and missing: + for variable, multiplier in zip(variables, multipliers): + if variable not in assignments: + domain = domains[variable] + for value in domain[:]: + if sum+value*multiplier > exactsum: + domain.hideValue(value) + if not domain: + return False + else: + for variable in variables: + if variable in assignments: + sum += assignments[variable] + else: + missing = True + if type(sum) is float: + sum = round(sum, 10) + if sum > exactsum: + return False + if forwardcheck and missing: + for variable in variables: + if variable not in assignments: + domain = domains[variable] + for value in domain[:]: + if sum+value > exactsum: + domain.hideValue(value) + if not domain: + return False + if missing: + return sum <= exactsum + else: + return sum == exactsum + +class MinSumConstraint(Constraint): + """ + Constraint enforcing that values of given variables sum at least + to a given amount + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> problem.addConstraint(MinSumConstraint(3)) + >>> sorted(sorted(x.items()) for x in problem.getSolutions()) + [[('a', 1), ('b', 2)], [('a', 2), ('b', 1)], [('a', 2), ('b', 2)]] + """#""" + + def __init__(self, minsum, multipliers=None): + """ + @param minsum: Value to be considered as the minimum sum + @type minsum: number + @param multipliers: If given, variable values will be multiplied by + the given factors before being summed to be checked + @type multipliers: sequence of numbers + """ + self._minsum = minsum + self._multipliers = multipliers + + def __call__(self, variables, domains, assignments, forwardcheck=False): + for variable in variables: + if variable not in assignments: + return True + else: + multipliers = self._multipliers + minsum = self._minsum + sum = 0 + if multipliers: + for variable, multiplier in zip(variables, multipliers): + sum += assignments[variable]*multiplier + else: + for variable in variables: + sum += assignments[variable] + if type(sum) is float: + sum = round(sum, 10) + return sum >= minsum + +class InSetConstraint(Constraint): + """ + Constraint enforcing that values of given variables are present in + the given set + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> problem.addConstraint(InSetConstraint([1])) + >>> sorted(sorted(x.items()) for x in problem.getSolutions()) + [[('a', 1), ('b', 1)]] + """#""" + + def __init__(self, set): + """ + @param set: Set of allowed values + @type set: set + """ + self._set = set + + def __call__(self, variables, domains, assignments, forwardcheck=False): + # preProcess() will remove it. + raise RuntimeError, "Can't happen" + + def preProcess(self, variables, domains, constraints, vconstraints): + set = self._set + for variable in variables: + domain = domains[variable] + for value in domain[:]: + if value not in set: + domain.remove(value) + vconstraints[variable].remove((self, variables)) + constraints.remove((self, variables)) + +class NotInSetConstraint(Constraint): + """ + Constraint enforcing that values of given variables are not present in + the given set + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> problem.addConstraint(NotInSetConstraint([1])) + >>> sorted(sorted(x.items()) for x in problem.getSolutions()) + [[('a', 2), ('b', 2)]] + """#""" + + def __init__(self, set): + """ + @param set: Set of disallowed values + @type set: set + """ + self._set = set + + def __call__(self, variables, domains, assignments, forwardcheck=False): + # preProcess() will remove it. + raise RuntimeError, "Can't happen" + + def preProcess(self, variables, domains, constraints, vconstraints): + set = self._set + for variable in variables: + domain = domains[variable] + for value in domain[:]: + if value in set: + domain.remove(value) + vconstraints[variable].remove((self, variables)) + constraints.remove((self, variables)) + +class SomeInSetConstraint(Constraint): + """ + Constraint enforcing that at least some of the values of given + variables must be present in a given set + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> problem.addConstraint(SomeInSetConstraint([1])) + >>> sorted(sorted(x.items()) for x in problem.getSolutions()) + [[('a', 1), ('b', 1)], [('a', 1), ('b', 2)], [('a', 2), ('b', 1)]] + """#""" + + def __init__(self, set, n=1, exact=False): + """ + @param set: Set of values to be checked + @type set: set + @param n: Minimum number of assigned values that should be present + in set (default is 1) + @type n: int + @param exact: Whether the number of assigned values which are + present in set must be exactly C{n} + @type exact: bool + """ + self._set = set + self._n = n + self._exact = exact + + def __call__(self, variables, domains, assignments, forwardcheck=False): + set = self._set + missing = 0 + found = 0 + for variable in variables: + if variable in assignments: + found += assignments[variable] in set + else: + missing += 1 + if missing: + if self._exact: + if not (found <= self._n <= missing+found): + return False + else: + if self._n > missing+found: + return False + if forwardcheck and self._n-found == missing: + # All unassigned variables must be assigned to + # values in the set. + for variable in variables: + if variable not in assignments: + domain = domains[variable] + for value in domain[:]: + if value not in set: + domain.hideValue(value) + if not domain: + return False + else: + if self._exact: + if found != self._n: + return False + else: + if found < self._n: + return False + return True + +class SomeNotInSetConstraint(Constraint): + """ + Constraint enforcing that at least some of the values of given + variables must not be present in a given set + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> problem.addConstraint(SomeNotInSetConstraint([1])) + >>> sorted(sorted(x.items()) for x in problem.getSolutions()) + [[('a', 1), ('b', 2)], [('a', 2), ('b', 1)], [('a', 2), ('b', 2)]] + """#""" + + def __init__(self, set, n=1, exact=False): + """ + @param set: Set of values to be checked + @type set: set + @param n: Minimum number of assigned values that should not be present + in set (default is 1) + @type n: int + @param exact: Whether the number of assigned values which are + not present in set must be exactly C{n} + @type exact: bool + """ + self._set = set + self._n = n + self._exact = exact + + def __call__(self, variables, domains, assignments, forwardcheck=False): + set = self._set + missing = 0 + found = 0 + for variable in variables: + if variable in assignments: + found += assignments[variable] not in set + else: + missing += 1 + if missing: + if self._exact: + if not (found <= self._n <= missing+found): + return False + else: + if self._n > missing+found: + return False + if forwardcheck and self._n-found == missing: + # All unassigned variables must be assigned to + # values not in the set. + for variable in variables: + if variable not in assignments: + domain = domains[variable] + for value in domain[:]: + if value in set: + domain.hideValue(value) + if not domain: + return False + else: + if self._exact: + if found != self._n: + return False + else: + if found < self._n: + return False + return True + +if __name__ == "__main__": + import doctest + doctest.testmod() + diff --git a/csp/python-constraint/trials/crosswords.py b/csp/python-constraint/trials/crosswords.py new file mode 100755 index 00000000..5cb502f0 --- /dev/null +++ b/csp/python-constraint/trials/crosswords.py @@ -0,0 +1,153 @@ +#!/usr/bin/python +from constraint import * +import random +import sys + +MINLEN = 3 + +def main(puzzle, lines): + puzzle = puzzle.rstrip().splitlines() + while puzzle and not puzzle[0]: + del puzzle[0] + + # Extract horizontal words + horizontal = [] + word = [] + predefined = {} + for row in range(len(puzzle)): + for col in range(len(puzzle[row])): + char = puzzle[row][col] + if not char.isspace(): + word.append((row, col)) + if char != "#": + predefined[row, col] = char + elif word: + if len(word) > MINLEN: + horizontal.append(word[:]) + del word[:] + if word: + if len(word) > MINLEN: + horizontal.append(word[:]) + del word[:] + + # Extract vertical words + vertical = [] + validcol = True + col = 0 + while validcol: + validcol = False + for row in range(len(puzzle)): + if col >= len(puzzle[row]): + if word: + if len(word) > MINLEN: + vertical.append(word[:]) + del word[:] + else: + validcol = True + char = puzzle[row][col] + if not char.isspace(): + word.append((row, col)) + if char != "#": + predefined[row, col] = char + elif word: + if len(word) > MINLEN: + vertical.append(word[:]) + del word[:] + if word: + if len(word) > MINLEN: + vertical.append(word[:]) + del word[:] + col += 1 + + hnames = ["h%d" % i for i in range(len(horizontal))] + vnames = ["v%d" % i for i in range(len(vertical))] + + #problem = Problem(MinConflictsSolver()) + problem = Problem() + + for hi, hword in enumerate(horizontal): + for vi, vword in enumerate(vertical): + for hchar in hword: + if hchar in vword: + hci = hword.index(hchar) + vci = vword.index(hchar) + problem.addConstraint(lambda hw, vw, hci=hci, vci=vci: + hw[hci] == vw[vci], + ("h%d" % hi, "v%d" % vi)) + + for char, letter in predefined.items(): + for hi, hword in enumerate(horizontal): + if char in hword: + hci = hword.index(char) + problem.addConstraint(lambda hw, hci=hci, letter=letter: + hw[hci] == letter, ("h%d" % hi,)) + for vi, vword in enumerate(vertical): + if char in vword: + vci = vword.index(char) + problem.addConstraint(lambda vw, vci=vci, letter=letter: + vw[vci] == letter, ("v%d" % vi,)) + + wordsbylen = {} + for hword in horizontal: + wordsbylen[len(hword)] = [] + for vword in vertical: + wordsbylen[len(vword)] = [] + + for line in lines: + line = line.strip() + l = len(line) + if l in wordsbylen: + wordsbylen[l].append(line.upper()) + + for hi, hword in enumerate(horizontal): + words = wordsbylen[len(hword)] + random.shuffle(words) + problem.addVariable("h%d" % hi, words) + for vi, vword in enumerate(vertical): + words = wordsbylen[len(vword)] + random.shuffle(words) + problem.addVariable("v%d" % vi, words) + + problem.addConstraint(AllDifferentConstraint()) + + solution = problem.getSolution() + if not solution: + print "No solution found!" + + maxcol = 0 + maxrow = 0 + for hword in horizontal: + for row, col in hword: + if row > maxrow: + maxrow = row + if col > maxcol: + maxcol = col + for vword in vertical: + for row, col in vword: + if row > maxrow: + maxrow = row + if col > maxcol: + maxcol = col + + matrix = [] + for row in range(maxrow+1): + matrix.append([" "]*(maxcol+1)) + + for variable in solution: + if variable[0] == "v": + word = vertical[int(variable[1:])] + else: + word = horizontal[int(variable[1:])] + for (row, col), char in zip(word, solution[variable]): + matrix[row][col] = char + + for row in range(maxrow+1): + for col in range(maxcol+1): + sys.stdout.write(matrix[row][col]) + sys.stdout.write("\n") + +if __name__ == "__main__": + if len(sys.argv) != 3: + sys.exit("Usage: crosswords.py ") + main(open(sys.argv[1]).read(), open(sys.argv[2])) + diff --git a/csp/python-constraint/trials/einstein.py b/csp/python-constraint/trials/einstein.py new file mode 100755 index 00000000..ede13f88 --- /dev/null +++ b/csp/python-constraint/trials/einstein.py @@ -0,0 +1,201 @@ +#!/usr/bin/python +# +# ALBERT EINSTEIN'S RIDDLE +# +# ARE YOU IN THE TOP 2% OF INTELLIGENT PEOPLE IN THE WORLD? +# SOLVE THE RIDDLE AND FIND OUT. +# +# There are no tricks, just pure logic, so good luck and don't give up. +# +# 1. In a street there are five houses, painted five different colours. +# 2. In each house lives a person of different nationality +# 3. These five homeowners each drink a different kind of beverage, smoke +# different brand of cigar and keep a different pet. +# +# THE QUESTION: WHO OWNS THE FISH? +# +# HINTS +# +# 1. The Brit lives in a red house. +# 2. The Swede keeps dogs as pets. +# 3. The Dane drinks tea. +# 4. The Green house is on the left of the White house. +# 5. The owner of the Green house drinks coffee. +# 6. The person who smokes Pall Mall rears birds. +# 7. The owner of the Yellow house smokes Dunhill. +# 8. The man living in the centre house drinks milk. +# 9. The Norwegian lives in the first house. +# 10. The man who smokes Blends lives next to the one who keeps cats. +# 11. The man who keeps horses lives next to the man who smokes Dunhill. +# 12. The man who smokes Blue Master drinks beer. +# 13. The German smokes Prince. +# 14. The Norwegian lives next to the blue house. +# 15. The man who smokes Blends has a neighbour who drinks water. +# +# ALBERT EINSTEIN WROTE THIS RIDDLE EARLY DURING THE 19th CENTURY. HE +# SAID THAT 98% OF THE WORLD POPULATION WOULD NOT BE ABLE TO SOLVE IT. + +from constraint import * + +# Check http://www.csc.fi/oppaat/f95/python/talot.py + +def main(): + problem = Problem() + for i in range(1,6): + problem.addVariable("color%d" % i, + ["red", "white", "green", "yellow", "blue"]) + problem.addVariable("nationality%d" % i, + ["brit", "swede", "dane", "norwegian", "german"]) + problem.addVariable("drink%d" % i, + ["tea", "coffee", "milk", "beer", "water"]) + problem.addVariable("smoke%d" % i, + ["pallmall", "dunhill", "blends", + "bluemaster", "prince"]) + problem.addVariable("pet%d" % i, + ["dogs", "birds", "cats", "horses", "fish"]) + + problem.addConstraint(AllDifferentConstraint(), + ["color%d" % i for i in range(1,6)]) + problem.addConstraint(AllDifferentConstraint(), + ["nationality%d" % i for i in range(1,6)]) + problem.addConstraint(AllDifferentConstraint(), + ["drink%d" % i for i in range(1,6)]) + problem.addConstraint(AllDifferentConstraint(), + ["smoke%d" % i for i in range(1,6)]) + problem.addConstraint(AllDifferentConstraint(), + ["pet%d" % i for i in range(1,6)]) + + for i in range(1,6): + + # Hint 1 + problem.addConstraint(lambda nationality, color: + nationality != "brit" or color == "red", + ("nationality%d" % i, "color%d" % i)) + + # Hint 2 + problem.addConstraint(lambda nationality, pet: + nationality != "swede" or pet == "dogs", + ("nationality%d" % i, "pet%d" % i)) + + # Hint 3 + problem.addConstraint(lambda nationality, drink: + nationality != "dane" or drink == "tea", + ("nationality%d" % i, "drink%d" % i)) + + # Hint 4 + if i < 5: + problem.addConstraint(lambda colora, colorb: + colora != "green" or colorb == "white", + ("color%d" % i, "color%d" % (i+1))) + else: + problem.addConstraint(lambda color: color != "green", + ("color%d" % i,)) + + # Hint 5 + problem.addConstraint(lambda color, drink: + color != "green" or drink == "coffee", + ("color%d" % i, "drink%d" % i)) + + # Hint 6 + problem.addConstraint(lambda smoke, pet: + smoke != "pallmall" or pet == "birds", + ("smoke%d" % i, "pet%d" % i)) + + # Hint 7 + problem.addConstraint(lambda color, smoke: + color != "yellow" or smoke == "dunhill", + ("color%d" % i, "smoke%d" % i)) + + # Hint 8 + if i == 3: + problem.addConstraint(lambda drink: drink == "milk", + ("drink%d" % i,)) + + # Hint 9 + if i == 1: + problem.addConstraint(lambda nationality: + nationality == "norwegian", + ("nationality%d" % i,)) + + # Hint 10 + if 1 < i < 5: + problem.addConstraint(lambda smoke, peta, petb: + smoke != "blends" or peta == "cats" or + petb == "cats", + ("smoke%d" % i, "pet%d" % (i-1), + "pet%d" % (i+1))) + else: + problem.addConstraint(lambda smoke, pet: + smoke != "blends" or pet == "cats", + ("smoke%d" % i, + "pet%d" % (i == 1 and 2 or 4))) + + # Hint 11 + if 1 < i < 5: + problem.addConstraint(lambda pet, smokea, smokeb: + pet != "horses" or smokea == "dunhill" or + smokeb == "dunhill", + ("pet%d" % i, "smoke%d" % (i-1), + "smoke%d" % (i+1))) + else: + problem.addConstraint(lambda pet, smoke: + pet != "horses" or smoke == "dunhill", + ("pet%d" % i, + "smoke%d" % (i == 1 and 2 or 4))) + + # Hint 12 + problem.addConstraint(lambda smoke, drink: + smoke != "bluemaster" or drink == "beer", + ("smoke%d" % i, "drink%d" % i)) + + # Hint 13 + problem.addConstraint(lambda nationality, smoke: + nationality != "german" or smoke == "prince", + ("nationality%d" % i, "smoke%d" % i)) + + # Hint 14 + if 1 < i < 5: + problem.addConstraint(lambda nationality, colora, colorb: + nationality != "norwegian" or + colora == "blue" or colorb == "blue", + ("nationality%d" % i, "color%d" % (i-1), + "color%d" % (i+1))) + else: + problem.addConstraint(lambda nationality, color: + nationality != "norwegian" or + color == "blue", + ("nationality%d" % i, + "color%d" % (i == 1 and 2 or 4))) + + # Hint 15 + if 1 < i < 5: + problem.addConstraint(lambda smoke, drinka, drinkb: + smoke != "blends" or + drinka == "water" or drinkb == "water", + ("smoke%d" % i, "drink%d" % (i-1), + "drink%d" % (i+1))) + else: + problem.addConstraint(lambda smoke, drink: + smoke != "blends" or drink == "water", + ("smoke%d" % i, + "drink%d" % (i == 1 and 2 or 4))) + + solutions = problem.getSolutions() + print "Found %d solution(s)!" % len(solutions) + print + for solution in solutions: + showSolution(solution) + +def showSolution(solution): + for i in range(1,6): + print "House %d" % i + print "--------" + print "Nationality: %s" % solution["nationality%d" % i] + print "Color: %s" % solution["color%d" % i] + print "Drink: %s" % solution["drink%d" % i] + print "Smoke: %s" % solution["smoke%d" % i] + print "Pet: %s" % solution["pet%d" % i] + print + +if __name__ == "__main__": + main() diff --git a/csp/python-constraint/trials/einstein2.py b/csp/python-constraint/trials/einstein2.py new file mode 100755 index 00000000..d1f7b86d --- /dev/null +++ b/csp/python-constraint/trials/einstein2.py @@ -0,0 +1,190 @@ +#!/usr/bin/python +# +# ALBERT EINSTEIN'S RIDDLE +# +# ARE YOU IN THE TOP 2% OF INTELLIGENT PEOPLE IN THE WORLD? +# SOLVE THE RIDDLE AND FIND OUT. +# +# There are no tricks, just pure logic, so good luck and don't give up. +# +# 1. In a street there are five houses, painted five different colours. +# 2. In each house lives a person of different nationality +# 3. These five homeowners each drink a different kind of beverage, smoke +# different brand of cigar and keep a different pet. +# +# THE QUESTION: WHO OWNS THE zebra? +# +# HINTS +# +# 1. The englishman lives in a red house. +# 2. The spaniard keeps dogs as pets. +# 5. The owner of the Green house drinks coffee. +# 3. The ukrainian drinks tea. +# 4. The Green house is on the left of the ivory house. +# 6. The person who smokes oldgold rears snails. +# 7. The owner of the Yellow house smokes kools. +# 8. The man living in the centre house drinks milk. +# 9. The Norwegian lives in the first house. +# 10. The man who smokes chesterfields lives next to the one who keeps foxes. +# 11. The man who keeps horses lives next to the man who smokes kools. +# 12. The man who smokes luckystrike drinks orangejuice. +# 13. The japanese smokes parliaments. +# 14. The Norwegian lives next to the blue house. +# 15. The man who smokes chesterfields has a neighbour who drinks water. +# +# ALBERT EINSTEIN WROTE THIS RIDDLE EARLY DURING THE 19th CENTURY. HE +# SAID THAT 98% OF THE WORLD POPULATION WOULD NOT BE ABLE TO SOLVE IT. + +from constraint import * + +# Check http://www.csc.fi/oppaat/f95/python/talot.py + +def main(): + problem = Problem() + for i in range(1,6): + problem.addVariable("color%d" % i, + ["red", "ivory", "green", "yellow", "blue"]) + problem.addVariable("nationality%d" % i, + ["englishman", "spaniard", "ukrainian", "norwegian", "japanese"]) + problem.addVariable("drink%d" % i, + ["tea", "coffee", "milk", "orangejuice", "water"]) + problem.addVariable("smoke%d" % i, + ["oldgold", "kools", "chesterfields", + "luckystrike", "parliaments"]) + problem.addVariable("pet%d" % i, + ["dogs", "snails", "foxes", "horses", "zebra"]) + + problem.addConstraint(AllDifferentConstraint(), + ["color%d" % i for i in range(1,6)]) + problem.addConstraint(AllDifferentConstraint(), + ["nationality%d" % i for i in range(1,6)]) + problem.addConstraint(AllDifferentConstraint(), + ["drink%d" % i for i in range(1,6)]) + problem.addConstraint(AllDifferentConstraint(), + ["smoke%d" % i for i in range(1,6)]) + problem.addConstraint(AllDifferentConstraint(), + ["pet%d" % i for i in range(1,6)]) + + for i in range(1,6): + + # Hint 1 + problem.addConstraint(lambda nationality, color: + nationality != "englishman" or color == "red", + ("nationality%d" % i, "color%d" % i)) + + # Hint 2 + problem.addConstraint(lambda nationality, pet: + nationality != "spaniard" or pet == "dogs", + ("nationality%d" % i, "pet%d" % i)) + + # Hint 3 + problem.addConstraint(lambda nationality, drink: + nationality != "ukrainian" or drink == "tea", + ("nationality%d" % i, "drink%d" % i)) + + # Hint 4 + if i < 5: + problem.addConstraint(lambda colora, colorb: + colora != "green" or colorb == "ivory", + ("color%d" % i, "color%d" % (i+1))) + else: + problem.addConstraint(lambda color: color != "green", + ("color%d" % i,)) + + # Hint 5 + problem.addConstraint(lambda color, drink: + color != "green" or drink == "coffee", + ("color%d" % i, "drink%d" % i)) + + # Hint 6 + problem.addConstraint(lambda smoke, pet: + smoke != "oldgold" or pet == "snails", + ("smoke%d" % i, "pet%d" % i)) + + # Hint 7 + problem.addConstraint(lambda color, smoke: + color != "yellow" or smoke == "kools", + ("color%d" % i, "smoke%d" % i)) + + # Hint 8 + if i == 3: + problem.addConstraint(lambda drink: drink == "milk", + ("drink%d" % i,)) + + # Hint 9 + if i == 1: + problem.addConstraint(lambda nationality: + nationality == "norwegian", + ("nationality%d" % i,)) + + # Hint 10 + if 1 < i < 5: + problem.addConstraint(lambda smoke, peta, petb: + smoke != "chesterfields" or peta == "foxes" or + petb == "foxes", + ("smoke%d" % i, "pet%d" % (i-1), + "pet%d" % (i+1))) + else: + problem.addConstraint(lambda smoke, pet: + smoke != "chesterfields" or pet == "foxes", + ("smoke%d" % i, + "pet%d" % (i == 1 and 2 or 4))) + + # Hint 11 + if 1 < i < 5: + problem.addConstraint(lambda pet, smokea, smokeb: + pet != "horses" or smokea == "kools" or + smokeb == "kools", + ("pet%d" % i, "smoke%d" % (i-1), + "smoke%d" % (i+1))) + else: + problem.addConstraint(lambda pet, smoke: + pet != "horses" or smoke == "kools", + ("pet%d" % i, + "smoke%d" % (i == 1 and 2 or 4))) + + # Hint 12 + problem.addConstraint(lambda smoke, drink: + smoke != "luckystrike" or drink == "orangejuice", + ("smoke%d" % i, "drink%d" % i)) + + # Hint 13 + problem.addConstraint(lambda nationality, smoke: + nationality != "japanese" or smoke == "parliaments", + ("nationality%d" % i, "smoke%d" % i)) + + # Hint 14 + if 1 < i < 5: + problem.addConstraint(lambda nationality, colora, colorb: + nationality != "norwegian" or + colora == "blue" or colorb == "blue", + ("nationality%d" % i, "color%d" % (i-1), + "color%d" % (i+1))) + else: + problem.addConstraint(lambda nationality, color: + nationality != "norwegian" or + color == "blue", + ("nationality%d" % i, + "color%d" % (i == 1 and 2 or 4))) + + + + solutions = problem.getSolutions() + print "Found %d solution(s)!" % len(solutions) + print + for solution in solutions: + showSolution(solution) + +def showSolution(solution): + for i in range(1,6): + print "House %d" % i + print "--------" + print "Nationality: %s" % solution["nationality%d" % i] + print "Color: %s" % solution["color%d" % i] + print "Drink: %s" % solution["drink%d" % i] + print "Smoke: %s" % solution["smoke%d" % i] + print "Pet: %s" % solution["pet%d" % i] + print + +if __name__ == "__main__": + main() diff --git a/csp/python-constraint/trials/large.mask b/csp/python-constraint/trials/large.mask new file mode 100644 index 00000000..ba5364c8 --- /dev/null +++ b/csp/python-constraint/trials/large.mask @@ -0,0 +1,27 @@ + +# ######## # +# # # # # +######## # # +# # # # # +# # ######## +# # # # # # +######## # # +# # # # # # + # # # +######## # # + # # # # # + # ######## + # # # # # + # # ######## + # # # # # # + # # ######## + # # # # +######## # # + # # # # # # + # # # # # # + ######## # # + # # # # + # ######## + # # # # +######## # # + diff --git a/csp/python-constraint/trials/medium.mask b/csp/python-constraint/trials/medium.mask new file mode 100644 index 00000000..3332a097 --- /dev/null +++ b/csp/python-constraint/trials/medium.mask @@ -0,0 +1,19 @@ + + # +######### +# # # +# # ###### +# # # +# # # # +# # # # +######## # +# # # + # # # + ######### + # # # + ######### + # # # + # # +####### + # + diff --git a/csp/python-constraint/trials/python.mask b/csp/python-constraint/trials/python.mask new file mode 100644 index 00000000..fe5a5767 --- /dev/null +++ b/csp/python-constraint/trials/python.mask @@ -0,0 +1,8 @@ + P + Y +####T#### + # H # + # O # +####N # + # # +######### diff --git a/csp/python-constraint/trials/queens.py b/csp/python-constraint/trials/queens.py new file mode 100755 index 00000000..deac7131 --- /dev/null +++ b/csp/python-constraint/trials/queens.py @@ -0,0 +1,47 @@ +#!/usr/bin/python +# +# http://mathworld.wolfram.com/QueensProblem.html +# +from constraint import * +import sys + +def main(show=False): + problem = Problem() + size = 8 + cols = range(size) + rows = range(size) + problem.addVariables(cols, rows) + for col1 in cols: + for col2 in cols: + if col1 < col2: + problem.addConstraint(lambda row1, row2, col1=col1, col2=col2: + abs(row1-row2) != abs(col1-col2) and + row1 != row2, (col1, col2)) + solutions = problem.getSolutions() + print "Found %d solution(s)!" % len(solutions) + if show: + for solution in solutions: + showSolution(solution, size) + +def showSolution(solution, size): + sys.stdout.write(" %s \n" % ("-"*((size*4)-1))) + for i in range(size): + sys.stdout.write(" |") + for j in range(size): + if solution[j] == i: + sys.stdout.write(" %d |" % j) + else: + sys.stdout.write(" |") + sys.stdout.write("\n") + if i != size-1: + sys.stdout.write(" |%s|\n" % ("-"*((size*4)-1))) + sys.stdout.write(" %s \n" % ("-"*((size*4)-1))) + +if __name__ == "__main__": + show = False + if len(sys.argv) == 2 and sys.argv[1] == "-s": + show = True + elif len(sys.argv) != 1: + sys.exit("Usage: queens.py [-s]") + main(show) + diff --git a/csp/python-constraint/trials/rooks.py b/csp/python-constraint/trials/rooks.py new file mode 100755 index 00000000..14f88b1e --- /dev/null +++ b/csp/python-constraint/trials/rooks.py @@ -0,0 +1,49 @@ +#!/usr/bin/python +# +# http://mathworld.wolfram.com/RooksProblem.html +# +from constraint import * +import sys + +def factorial(x): return x == 1 or factorial(x-1)*x + +def main(show=False): + problem = Problem() + size = 8 + cols = range(size) + rows = range(size) + problem.addVariables(cols, rows) + for col1 in cols: + for col2 in cols: + if col1 < col2: + problem.addConstraint(lambda row1, row2: row1 != row2, + (col1, col2)) + solutions = problem.getSolutions() + print "Found %d solution(s)!" % len(solutions) + assert len(solutions) == factorial(size) + if show: + for solution in solutions: + showSolution(solution, size) + +def showSolution(solution, size): + sys.stdout.write(" %s \n" % ("-"*((size*4)-1))) + for i in range(size): + sys.stdout.write(" |") + for j in range(size): + if solution[j] == i: + sys.stdout.write(" %d |" % j) + else: + sys.stdout.write(" |") + sys.stdout.write("\n") + if i != size-1: + sys.stdout.write(" |%s|\n" % ("-"*((size*4)-1))) + sys.stdout.write(" %s \n" % ("-"*((size*4)-1))) + +if __name__ == "__main__": + show = False + if len(sys.argv) == 2 and sys.argv[1] == "-s": + show = True + elif len(sys.argv) != 1: + sys.exit("Usage: rooks.py [-s]") + main(show) + diff --git a/csp/python-constraint/trials/seisseisdoze.py b/csp/python-constraint/trials/seisseisdoze.py new file mode 100755 index 00000000..b17956db --- /dev/null +++ b/csp/python-constraint/trials/seisseisdoze.py @@ -0,0 +1,32 @@ +#!/usr/bin/python +# +# Assign equal values to equal letters, and different values to +# different letters, in a way that satisfies the following sum: +# +# SEIS +# + SEIS +# ------ +# DOZE +# +from constraint import * + +def main(): + problem = Problem() + problem.addVariables("seidoz", range(10)) + problem.addConstraint(lambda s, e: (2*s)%10 == e, "se") + problem.addConstraint(lambda i, s, z, e: ((10*2*i)+(2*s))%100 == z*10+e, + "isze") + problem.addConstraint(lambda s, e, i, d, o, z: + 2*(s*1000+e*100+i*10+s) == d*1000+o*100+z*10+e, + "seidoz") + problem.addConstraint(lambda s: s != 0, "s") + problem.addConstraint(lambda d: d != 0, "d") + problem.addConstraint(AllDifferentConstraint()) + print "SEIS+SEIS=DOZE" + for s in problem.getSolutions(): + print ("%(s)d%(e)d%(i)d%(s)s+%(s)d%(e)d%(i)d%(s)d=" + "%(d)d%(o)d%(z)d%(e)d") % s + +if __name__ == "__main__": + main() + diff --git a/csp/python-constraint/trials/sendmoremoney.py b/csp/python-constraint/trials/sendmoremoney.py new file mode 100755 index 00000000..894b0cd5 --- /dev/null +++ b/csp/python-constraint/trials/sendmoremoney.py @@ -0,0 +1,34 @@ +#!/usr/bin/python +# +# Assign equal values to equal letters, and different values to +# different letters, in a way that satisfies the following sum: +# +# SEND +# + MORE +# ------ +# MONEY +# +from constraint import * + +def main(): + problem = Problem() + problem.addVariables("sendmory", range(10)) + problem.addConstraint(lambda d, e, y: (d+e)%10 == y, "dey") + problem.addConstraint(lambda n, d, r, e, y: (n*10+d+r*10+e)%100 == e*10+y, + "ndrey") + problem.addConstraint(lambda e, n, d, o, r, y: + (e*100+n*10+d+o*100+r*10+e)%1000 == n*100+e*10+y, + "endory") + problem.addConstraint(lambda s, e, n, d, m, o, r, y: + 1000*s+100*e+10*n+d + 1000*m+100*o+10*r+e == + 10000*m+1000*o+100*n+10*e+y, "sendmory") + problem.addConstraint(NotInSetConstraint([0]), "sm") + problem.addConstraint(AllDifferentConstraint()) + print "SEND+MORE=MONEY" + for s in problem.getSolutions(): + print "%(s)d%(e)d%(n)d%(d)d+" \ + "%(m)d%(o)d%(r)d%(e)d=" \ + "%(m)d%(o)d%(n)d%(e)d%(y)d" % s + +if __name__ == "__main__": + main() diff --git a/csp/python-constraint/trials/small.mask b/csp/python-constraint/trials/small.mask new file mode 100644 index 00000000..0e43ff78 --- /dev/null +++ b/csp/python-constraint/trials/small.mask @@ -0,0 +1,8 @@ + # + # +######### + # # + # # # # +##### # # + # # # +######### diff --git a/csp/python-constraint/trials/studentdesks.py b/csp/python-constraint/trials/studentdesks.py new file mode 100755 index 00000000..e8d47792 --- /dev/null +++ b/csp/python-constraint/trials/studentdesks.py @@ -0,0 +1,39 @@ +#!/usr/bin/python +# +# http://home.chello.no/~dudley/ +# +from constraint import * +import sys + +STUDENTDESKS = [[ 0, 1, 0, 0, 0, 0], + [ 0, 2, 3, 4, 5, 6], + [ 0, 7, 8, 9, 10, 0], + [ 0, 11, 12, 13, 14, 0], + [ 15, 16, 17, 18, 19, 0], + [ 0, 0, 0, 0, 20, 0]] + +def main(): + problem = Problem() + problem.addVariables(range(1,21), ["A", "B", "C", "D", "E"]) + problem.addConstraint(SomeInSetConstraint(["A"], 4, True)) + problem.addConstraint(SomeInSetConstraint(["B"], 4, True)) + problem.addConstraint(SomeInSetConstraint(["C"], 4, True)) + problem.addConstraint(SomeInSetConstraint(["D"], 4, True)) + problem.addConstraint(SomeInSetConstraint(["E"], 4, True)) + for row in range(len(STUDENTDESKS)-1): + for col in range(len(STUDENTDESKS[row])-1): + lst = [STUDENTDESKS[row][col], STUDENTDESKS[row][col+1], + STUDENTDESKS[row+1][col], STUDENTDESKS[row+1][col+1]] + lst = [x for x in lst if x] + problem.addConstraint(AllDifferentConstraint(), lst) + showSolution(problem.getSolution()) + +def showSolution(solution): + for row in range(len(STUDENTDESKS)): + for col in range(len(STUDENTDESKS[row])): + id = STUDENTDESKS[row][col] + sys.stdout.write(" %s" % (id and solution[id] or " ")) + sys.stdout.write("\n") + +if __name__ == "__main__": + main() diff --git a/csp/python-constraint/trials/sudoku.py b/csp/python-constraint/trials/sudoku.py new file mode 100644 index 00000000..e79698ea --- /dev/null +++ b/csp/python-constraint/trials/sudoku.py @@ -0,0 +1,61 @@ +# +# Sudoku puzzle solver by by Luigi Poderico (www.poderico.it). +# +from constraint import * + +problem = Problem() + +# Define the variables: 9 rows of 9 variables rangin in 1...9 +for i in range(1, 10) : + problem.addVariables(range(i*10+1, i*10+10), range(1, 10)) + +# Each row has different values +for i in range(1, 10) : + problem.addConstraint(AllDifferentConstraint(), range(i*10+1, i*10+10)) + +# Each colum has different values +for i in range(1, 10) : + problem.addConstraint(AllDifferentConstraint(), range(10+i, 100+i, 10)) + +# Each 3x3 box has different values +problem.addConstraint(AllDifferentConstraint(), [11,12,13,21,22,23,31,32,33]) +problem.addConstraint(AllDifferentConstraint(), [41,42,43,51,52,53,61,62,63]) +problem.addConstraint(AllDifferentConstraint(), [71,72,73,81,82,83,91,92,93]) + +problem.addConstraint(AllDifferentConstraint(), [14,15,16,24,25,26,34,35,36]) +problem.addConstraint(AllDifferentConstraint(), [44,45,46,54,55,56,64,65,66]) +problem.addConstraint(AllDifferentConstraint(), [74,75,76,84,85,86,94,95,96]) + +problem.addConstraint(AllDifferentConstraint(), [17,18,19,27,28,29,37,38,39]) +problem.addConstraint(AllDifferentConstraint(), [47,48,49,57,58,59,67,68,69]) +problem.addConstraint(AllDifferentConstraint(), [77,78,79,87,88,89,97,98,99]) + +# Some value is given. +initValue = [[0, 9, 0, 7, 0, 0, 8, 6, 0], + [0, 3, 1, 0, 0, 5, 0, 2, 0], + [8, 0, 6, 0, 0, 0, 0, 0, 0], + [0, 0, 7, 0, 5, 0, 0, 0, 6], + [0, 0, 0, 3, 0, 7, 0, 0, 0], + [5, 0, 0, 0, 1, 0, 7, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 0, 9], + [0, 2, 0, 6, 0, 0, 0, 5, 0], + [0, 5, 4, 0, 0, 8, 0, 7, 0]] + +for i in range(1, 10) : + for j in range(1, 10): + if initValue[i-1][j-1] !=0 : + problem.addConstraint(lambda var, val=initValue[i-1][j-1]: + var==val, (i*10+j,)) + +# Get the solutions. +solutions = problem.getSolutions() + +# Print the solutions +for solution in solutions: + for i in range(1, 10): + for j in range(1, 10): + index = i*10+j + print solution[index], + print + print + diff --git a/csp/python-constraint/trials/twotwofour.py b/csp/python-constraint/trials/twotwofour.py new file mode 100755 index 00000000..b9e70d6a --- /dev/null +++ b/csp/python-constraint/trials/twotwofour.py @@ -0,0 +1,28 @@ +#!/usr/bin/python +# +# Assign equal values to equal letters, and different values to +# different letters, in a way that satisfies the following sum: +# +# TWO +# + TWO +# ----- +# FOUR +# +from constraint import * + +def main(): + problem = Problem() + problem.addVariables("twofur", range(10)) + problem.addConstraint(lambda o, r: (2*o)%10 == r, "or") + problem.addConstraint(lambda w, o, u, r: ((10*2*w)+(2*o))%100 == u*10+r, + "wour") + problem.addConstraint(lambda t, w, o, f, u, r: + 2*(t*100+w*10+o) == f*1000+o*100+u*10+r, "twofur") + problem.addConstraint(NotInSetConstraint([0]), "ft") + problem.addConstraint(AllDifferentConstraint()) + print "TWO+TWO=FOUR" + for s in problem.getSolutions(): + print "%(t)d%(w)d%(o)d+%(t)d%(w)d%(o)d=%(f)d%(o)d%(u)d%(r)d" % s + +if __name__ == "__main__": + main() diff --git a/csp/python-constraint/trials/xsum.py b/csp/python-constraint/trials/xsum.py new file mode 100755 index 00000000..0f5f70b6 --- /dev/null +++ b/csp/python-constraint/trials/xsum.py @@ -0,0 +1,37 @@ +#!/usr/bin/python +# +# Reorganize the following numbers in a way that each line of +# 5 numbers sum to 27. +# +# 1 6 +# 2 7 +# 3 +# 8 4 +# 9 5 +# +from constraint import * + +def main(): + problem = Problem() + problem.addVariables("abcdxefgh", range(1,10)) + problem.addConstraint(lambda a, b, c, d, x: + a < b < c < d and a+b+c+d+x == 27, "abcdx") + problem.addConstraint(lambda e, f, g, h, x: + e < f < g < h and e+f+g+h+x == 27, "efghx") + problem.addConstraint(AllDifferentConstraint()) + solutions = problem.getSolutions() + print "Found %d solutions!" % len(solutions) + showSolutions(solutions) + +def showSolutions(solutions): + for solution in solutions: + print " %d %d" % (solution["a"], solution["e"]) + print " %d %d " % (solution["b"], solution["f"]) + print " %d " % (solution["x"],) + print " %d %d " % (solution["g"], solution["c"]) + print " %d %d" % (solution["h"], solution["d"]) + print + +if __name__ == "__main__": + main() +