diff --git a/.gitignore b/.gitignore index 1fcb152..f3e598a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ out +.idea +.gradle diff --git a/EinsteinsRiddle/EinsteinsRiddle.iml b/EinsteinsRiddle/EinsteinsRiddle.iml index 1d1bbb2..35e74bb 100644 --- a/EinsteinsRiddle/EinsteinsRiddle.iml +++ b/EinsteinsRiddle/EinsteinsRiddle.iml @@ -125,6 +125,17 @@ + + + + + + + + + + + diff --git a/EinsteinsRiddle/README.md b/EinsteinsRiddle/README.md index cd023ca..5527d41 100644 --- a/EinsteinsRiddle/README.md +++ b/EinsteinsRiddle/README.md @@ -3,11 +3,21 @@ Einstein's Riddle Solves a logic puzzle using the following approaches: -* prolog directly +* prolog directly (for comparative purposes) * prolog underneath a Groovy DSL -* a constraint solving library beneath a Groovy DSL +* the __choco__ constraint solving library beneath a Groovy DSL +* the __jacop__ constraint solving library beneath a Groovy DSL + +The interesting thing to note is that the "business rules" are the same in all cases. The "DSL helper code" would typically be hidden from the user. The [prolog4j](https://github.com/espakm/prolog4j) generic prolog interface api is used along with the [tuprolog](http://tuprolog.alice.unibo.it/) prolog engine but you can try some of the other engines supported by prolog4j if you wish. -The [choco](http://www.emn.fr/z-info/choco-solver/) constraint solving library is used. \ No newline at end of file +The [choco](http://www.emn.fr/z-info/choco-solver/) and [JaCoP](http://jacop.osolpro.com/) constraint solving libraries are used. +These libraries offer similar features as far as this problem is concerned. Given that the JaCoP package isn't available in a public +Maven repository and has a restrictive GPL license, we have a preference for Choco for this example; but see the respective +documentation of the two packages to see which better suits your needs. + +To install the library needed for JaCoP you will need to run the following command (windows command shown): + + ..\gradlew downloadJaCoP diff --git a/EinsteinsRiddle/build.gradle b/EinsteinsRiddle/build.gradle new file mode 100644 index 0000000..2d1c9e9 --- /dev/null +++ b/EinsteinsRiddle/build.gradle @@ -0,0 +1,4 @@ +task downloadJaCoP() { + def name = 'JaCoP-3.1.2' + ant.get(src: "http://downloads.sourceforge.net/project/jacop-solver/$name/${name}.jar", dest: "lib") +} diff --git a/EinsteinsRiddle/src/jacop/JacopSolverDSL.groovy b/EinsteinsRiddle/src/jacop/JacopSolverDSL.groovy new file mode 100644 index 0000000..c6c6d61 --- /dev/null +++ b/EinsteinsRiddle/src/jacop/JacopSolverDSL.groovy @@ -0,0 +1,181 @@ +package jacop + +import JaCoP.core.* +import JaCoP.constraints.* +import JaCoP.search.* +import groovy.transform.Field + +enum Pet { dog, cat, bird, fish, horse } +enum Color { green, white, red, blue, yellow } +enum Sport { baseball, volleyball, football, hockey, tennis } +enum Drink { water, tea, milk, coffee, beer } +enum Nationality { Norwegian, Dane, Briton, German, Swede } + +import static Pet.* +import static Color.* +import static Sport.* +import static Drink.* +import static Nationality.* + +// define logic solver data structures +num = 5 +center = 2 +first = 0 +println "Solving Einstein's Riddle:" + +@Field s = new Store() + +def makeEnumVar(String st, Object[] arr) { new IntVar(s, st, 0, arr.size()-1) } +pets = new IntVar[num] +colors = new IntVar[num] +plays = new IntVar[num] +drinks = new IntVar[num] +nations = new IntVar[num] + +(0.. + pets[i] = makeEnumVar("pet$i", pets) + colors[i] = makeEnumVar("color$i", colors) + plays[i] = makeEnumVar("plays$i", plays) + drinks[i] = makeEnumVar("drink$i", drinks) + nations[i] = makeEnumVar("nation$i", nations) +} + +def pretty(enumClass, selected) { enumClass.values().find{ it.ordinal().toString() == selected.toString() } } + +// define DSL (simplistic non-refactored version) +def neighbours(var1, val1, var2, val2) { + s.impose and( + ifOnlyIf(eq(var1[0], val1), eq(var2[1], val2)), + implies(eq(var1[1], val1), or(eq(var2[0], val2), eq(var2[2], val2))), + implies(eq(var1[2], val1), or(eq(var2[1], val2), eq(var2[3], val2))), + implies(eq(var1[3], val1), or(eq(var2[2], val2), eq(var2[4], val2))), + ifOnlyIf(eq(var1[4], val1), eq(var2[3], val2)) + ) +} +iff = { e1, c1, e2, c2 -> s.impose and(*(0.. s.impose eq(a, b) } + +dogs = dog; birds = bird; cats = cat; horses = horse +a = owner = house = the = abode = person = man = to = is = side = next = who = different = 'ignored' + +// define the DSL in terms of DSL implementation +def the(Nationality n) { + def ctx = [nations, n] + [ + drinks:iff.curry(*ctx, drinks), + plays:iff.curry(*ctx, plays), + keeps:iff.curry(*ctx, pets), + rears:iff.curry(*ctx, pets), + owns:{ _the -> [first:{ house -> isEq(nations[first], n)}] }, + has:{ _a -> + [pet:iff.curry(*ctx, pets)] + Color.values().collectEntries{ c -> + [c.toString(), { _dummy -> iff(*ctx, colors, c) } ] + } + }, + lives: { _next -> [to: { _the -> + Color.values().collectEntries{ c -> + [c.toString(), { _dummy -> neighbours(*ctx, colors, c) } ] + } + }]} + ] +} + +def the(Color c1) {[ + house: { _is -> [on: { _the -> [left: { _side -> [of: { __the -> + Color.values().collectEntries{ c2 -> [c2.toString(), { _dummy -> + s.impose and(*(1.. + Color.values().collectEntries{ c -> [c.toString(), { _house -> [ + drinks:iff.curry(colors, c, drinks), + plays:iff.curry(colors, c, plays) + ] } ] } + }, + known: { _to -> [ + play: { sport -> + def ctx = [plays, sport] + [ + rears: iff.curry(*ctx, pets), + keeps: iff.curry(*ctx, pets), + drinks: iff.curry(*ctx, drinks), + lives: { _next -> [to: { _the -> [one: { _who -> [ + keeps: { pet -> neighbours(pets, pet, *ctx) }, + drinks: { beverage -> neighbours(drinks, beverage, *ctx) } + ]}]}]} + ] + }, + keep : { pet -> [ + lives: { _next -> [to: { _the -> [man: { _who -> [ + plays: { sport -> neighbours(pets, pet, plays, sport) } + ]}]}]} + ]} + ]}, + from: { _the -> [center: { house -> + [drinks: { d -> isEq(drinks[center], d)}] + }]} +]} + +def all(IntVar[] var) { + [are: { _different -> s.impose new Alldifferent(var) } ] +} + +// define rules +all pets are different +all colors are different +all plays are different +all drinks are different +all nations are different +the man from the center house drinks milk +the Norwegian owns the first house +the Dane drinks tea +the German plays hockey +the Swede keeps dogs // alternate ending: has a pet dog +the Briton has a red house // alternate ending: red abode +the owner of the green house drinks coffee +the owner of the yellow house plays baseball +the person known to play football rears birds // alternate ending: keeps birds +the man known to play tennis drinks beer +the green house is on the left side of the white house +the man known to play volleyball lives next to the one who keeps cats +the man known to keep horses lives next to the man who plays baseball +the man known to play volleyball lives next to the one who drinks water +the Norwegian lives next to the blue house + +// invoke logic solver +def vars = [pets, plays, drinks, colors, nations] +def search = new DepthFirstSearch() +def select = new SimpleMatrixSelect(vars as Var[][], new IndomainMin()) +search.solutionListener.searchAll(true) +def result = search.labeling(s, select) +println "Solutions found: " + result +search.solutionListener.solutionsNo().times { i -> + println "Solution ${i + 1}:" + def sol = search.getSolution(i + 1) + def ps = sol.take(5); sol = sol.drop(5) + def ss = sol.take(5); sol = sol.drop(5) + def ds = sol.take(5); sol = sol.drop(5) + def cs = sol.take(5); sol = sol.drop(5) + def ns = sol.take(5) + 5.times { + printSol(ns[it], ps[it], ss[it], ds[it], cs[it]) + } +} + +def printSol(n, p, s, d, c) { + print 'The ' + pretty(Nationality, n) + print ' has a pet ' + pretty(Pet, p) + print ' plays ' + pretty(Sport, s) + print ' drinks ' + pretty(Drink, d) + println ' and lives in a ' + pretty(Color, c) + ' house' +} + +def eq(IntVar x, IntVar y) { new XeqY(x, y) } +def eq(IntVar x, enumVar) { new XeqC(x, enumVar.ordinal()) } +def or(PrimitiveConstraint c1, PrimitiveConstraint c2) { new Or(c1, c2) } +def and(PrimitiveConstraint[] cs) { new And(cs) } +def ifOnlyIf(PrimitiveConstraint c1, PrimitiveConstraint c2) { new Eq(c1, c2) } +def implies(PrimitiveConstraint c1, PrimitiveConstraint c2) { new IfThen(c1, c2) } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..8acea11 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..842a29d --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon May 02 20:57:32 EST 2011 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=http\://gradle.artifactoryonline.com/gradle/distributions/gradle-0.9.2-bin.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..d8809f1 --- /dev/null +++ b/gradlew @@ -0,0 +1,168 @@ +#!/bin/bash + +############################################################################## +## ## +## Gradle wrapper script for UN*X ## +## ## +############################################################################## + +# Uncomment those lines to set JVM options. GRADLE_OPTS and JAVA_OPTS can be used together. +# GRADLE_OPTS="$GRADLE_OPTS -Xmx512m" +# JAVA_OPTS="$JAVA_OPTS -Xmx512m" + +GRADLE_APP_NAME=Gradle + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set JAVA_HOME if it's not already set. +if [ -z "$JAVA_HOME" ] ; then + if $darwin ; then + [ -z "$JAVA_HOME" -a -d "/Library/Java/Home" ] && export JAVA_HOME="/Library/Java/Home" + [ -z "$JAVA_HOME" -a -d "/System/Library/Frameworks/JavaVM.framework/Home" ] && export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Home" + else + javaExecutable="`which javac`" + [ -z "$javaExecutable" -o "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ] && die "JAVA_HOME not set and cannot find javac to deduce location, please set JAVA_HOME." + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + [ `expr "$readLink" : '\([^ ]*\)'` = "no" ] && die "JAVA_HOME not set and readlink not available, please set JAVA_HOME." + javaExecutable="`readlink -f \"$javaExecutable\"`" + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + export JAVA_HOME="$javaHome" + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVACMD" ] && JAVACMD=`cygpath --unix "$JAVACMD"` + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +STARTER_MAIN_CLASS=org.gradle.wrapper.GradleWrapperMain +CLASSPATH=`dirname "$0"`/gradle/wrapper/gradle-wrapper.jar +WRAPPER_PROPERTIES=`dirname "$0"`/gradle/wrapper/gradle-wrapper.properties +# Determine the Java command to use to start the JVM. +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="java" + fi +fi +if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi +if [ -z "$JAVA_HOME" ] ; then + warn "JAVA_HOME environment variable is not set" +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query businessSystem maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add GRADLE_APP_NAME to the JAVA_OPTS as -Xdock:name +if $darwin; then + JAVA_OPTS="$JAVA_OPTS -Xdock:name=$GRADLE_APP_NAME" +# we may also want to set -Xdock:image +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + JAVA_HOME=`cygpath --path --mixed "$JAVA_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +GRADLE_APP_BASE_NAME=`basename "$0"` + +exec "$JAVACMD" $JAVA_OPTS $GRADLE_OPTS \ + -classpath "$CLASSPATH" \ + -Dorg.gradle.appname="$GRADLE_APP_BASE_NAME" \ + -Dorg.gradle.wrapper.properties="$WRAPPER_PROPERTIES" \ + $STARTER_MAIN_CLASS \ + "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..479fddd --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,82 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem ## +@rem Gradle startup script for Windows ## +@rem ## +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Uncomment those lines to set JVM options. GRADLE_OPTS and JAVA_OPTS can be used together. +@rem set GRADLE_OPTS=%GRADLE_OPTS% -Xmx512m +@rem set JAVA_OPTS=%JAVA_OPTS% -Xmx512m + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=.\ + +@rem Find java.exe +set JAVA_EXE=java.exe +if not defined JAVA_HOME goto init + +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. +echo. +goto end + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set STARTER_MAIN_CLASS=org.gradle.wrapper.GradleWrapperMain +set CLASSPATH=%DIRNAME%\gradle\wrapper\gradle-wrapper.jar +set WRAPPER_PROPERTIES=%DIRNAME%\gradle\wrapper\gradle-wrapper.properties + +set GRADLE_OPTS=%JAVA_OPTS% %GRADLE_OPTS% -Dorg.gradle.wrapper.properties="%WRAPPER_PROPERTIES%" + +@rem Execute Gradle +"%JAVA_EXE%" %GRADLE_OPTS% -classpath "%CLASSPATH%" %STARTER_MAIN_CLASS% %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +if not "%OS%"=="Windows_NT" echo 1 > nul | choice /n /c:1 + +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit "%ERRORLEVEL%" +exit /b "%ERRORLEVEL%" + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega \ No newline at end of file