├── .gitignore ├── LICENSE ├── README.rst ├── project.clj └── src └── paip ├── 11_logic ├── unification.clj └── unification_test.clj ├── 2_grammar ├── grammar_as_lisp.clj └── grammar_rule_based.clj ├── 4_gps └── gps_basic.clj ├── 6_tools ├── pat_match.clj ├── pat_match_test.clj ├── search.clj └── search_test.clj ├── core.clj ├── utils.clj └── utils_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | pom.xml.asc 3 | *jar 4 | /lib/ 5 | /classes/ 6 | target 7 | /checkouts/ 8 | .lein-deps-sum 9 | .lein-repl-history 10 | .lein-plugins/ 11 | .lein-failures 12 | .nrepl-port 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | PAIP in Clojure 2 | --------------- 3 | 4 | Peter Norvig's *"Paradigms of Artificial Intelligence Programming: Case Studies 5 | in Common Lisp"* (a.k.a. PAIP) is one of my all-time favorite programming books. 6 | I think it's amazing and well worth going through even if you're not interested 7 | in Lisp or AI, just for the depth and breadth of solid advice and excellent code 8 | it contains. 9 | 10 | This project is my attempt to go through PAIP again, this time meticulously 11 | reimplementing all programs in `Clojure `_, a modern flavor 12 | of Lisp running on the JVM or in the browser. 13 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject paip "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Public Domain" 5 | :url "http://unlicense.org"} 6 | :dependencies [[org.clojure/clojure "1.8.0"] 7 | [org.clojure/tools.trace "0.7.9"]] 8 | :main ^:skip-aot paip.core 9 | :target-path "target/%s" 10 | :plugins [[lein-cljfmt "0.9.0"]] 11 | :profiles {:uberjar {:aot :all}}) 12 | -------------------------------------------------------------------------------- /src/paip/11_logic/unification.clj: -------------------------------------------------------------------------------- 1 | (ns paip.11-logic.unification 2 | (:use clojure.tools.trace) 3 | (:use paip.utils)) 4 | 5 | (def fail 6 | "Denotes a failure in matching" 7 | nil) 8 | 9 | (def no-bindings 10 | "Denotes successful match with no variable bindings" 11 | {}) 12 | 13 | (defn variable? 14 | [x] 15 | (and (symbol? x) (= \? (get (str x) 0)))) 16 | 17 | (defn get-binding 18 | "Find a variable->value binding in the given binding." 19 | [v bindings] 20 | (get bindings v)) 21 | 22 | (defn extend-bindings 23 | "Add a v -> value mappping to bindings." 24 | [v value bindings] 25 | (assoc bindings v value)) 26 | 27 | (defn match-variable 28 | "Does v match input? Uses (or updates) and returns bindings." 29 | [v input bindings] 30 | (let [b (get-binding v bindings)] 31 | (cond (nil? b) (extend-bindings v input bindings) 32 | (= input b) bindings 33 | :else fail))) 34 | 35 | (declare unify-variable occurs-check?) 36 | 37 | (defn unify 38 | "Unify x and y with the given bindings." 39 | ([x y] (unify x y no-bindings)) 40 | ([x y bindings] 41 | (cond (= bindings fail) fail 42 | (= x y) bindings 43 | (variable? x) (unify-variable x y bindings) 44 | (variable? y) (unify-variable y x bindings) 45 | (and (list? x) (list? y)) (unify 46 | (rest x) 47 | (rest y) 48 | (unify (first x) (first y) bindings)) 49 | :else fail))) 50 | 51 | (defn unify-variable 52 | "Unify v with x, using and maybe extending bindings." 53 | [v x bindings] 54 | (cond (get-binding v bindings) (unify (get-binding v bindings) x bindings) 55 | (and (variable? x) (get-binding x bindings)) (unify 56 | v 57 | (get-binding x bindings) 58 | bindings) 59 | (occurs-check? v x bindings) fail 60 | :else (extend-bindings v x bindings))) 61 | 62 | (defn occurs-check? 63 | "Does v occur anywhere inside x?" 64 | [v x bindings] 65 | (cond (= v x) true 66 | (and (variable? x) 67 | (get-binding x bindings)) (occurs-check? 68 | v 69 | (get-binding x bindings) 70 | bindings) 71 | (cons? x) (or (occurs-check? v (first x) bindings) 72 | (occurs-check? v (rest x) bindings)) 73 | :else false)) 74 | 75 | ;(trace-vars occurs-check) 76 | 77 | (unify '(?x + 1) '(2 + ?y)) 78 | -------------------------------------------------------------------------------- /src/paip/11_logic/unification_test.clj: -------------------------------------------------------------------------------- 1 | (ns paip.11-logic.unification-test 2 | (:use clojure.test) 3 | (:use paip.11-logic.unification)) 4 | 5 | (deftest unify-simple 6 | (is (= {} (unify 'a 'a))) 7 | (is (= {} (unify '(?x ?y) '(?x ?y)))) 8 | (is (nil? (unify 'a 'b))) 9 | 10 | (is (= {'?x 2} (unify '(?x + 1) '(2 + 1)))) 11 | (is (= {'?y 1} (unify '(2 + 1) '(2 + ?y)))) 12 | 13 | (is (= {'?x 2, '?y 1} (unify '(?x + 1) '(2 + ?y)))) 14 | (is (= {'?x '?y} (unify '?x '?y))) 15 | (is (= {'?x '?y} (unify '(?x ?x) '(?y ?y)))) 16 | (is (= {'?x '?y} (unify '(?x ?x ?x) '(?y ?y ?y)))) 17 | (is (= {'?x '?y} (unify '(?x ?y) '(?y ?x)))) 18 | (is (= {'?x '?y, '?a '?y} (unify '(?x ?y ?a) '(?y ?x ?x)))) 19 | 20 | (is (nil? (unify '(?x aa) 'aa)))) 21 | 22 | (deftest unify-occurs-fail 23 | (is (nil? (unify '?x '(f ?x)))) 24 | (is (nil? (unify '(?x ?y) '((f ?y) (f ?x)))))) 25 | 26 | (run-tests) 27 | -------------------------------------------------------------------------------- /src/paip/2_grammar/grammar_as_lisp.clj: -------------------------------------------------------------------------------- 1 | ;;; Chapter 2: "A simple Lisp Program" - generative grammar as Lisp functions. 2 | ;;; 3 | ;;; Notes: 4 | ;;; - Using lists, similar to the original PAIP code. 5 | ;;; - Clojure code gets compiled, so functions need to be known before they are 6 | ;;; used. We can either modify the definition order or use 'declare'. 7 | 8 | (ns paip.2-grammar.grammar_as_lisp) 9 | 10 | (defn one-of 11 | "Pick one element of s and make a list of it" 12 | [s] 13 | (list (rand-nth s))) 14 | 15 | (declare noun-phrase) 16 | 17 | (defn Prep [] (one-of '(to in by with on))) 18 | (defn PP [] (concat (Prep) (noun-phrase))) 19 | (defn Adj [] (one-of '(bit little blue green adiabatic))) 20 | 21 | (defn Adj* [] 22 | ;; One approach to selection with 50% probability 23 | (if (= (rand-int 2) 0) 24 | nil 25 | (concat (Adj) (Adj*)))) 26 | 27 | (defn PP* [] 28 | ;; Another approach to selection with 50% probability 29 | (if (rand-nth '(true false)) 30 | (concat (PP) (PP*)) 31 | nil)) 32 | 33 | (defn Article [] (one-of '(the a))) 34 | (defn Noun [] (one-of '(man ball woman table))) 35 | (defn Verb [] (one-of '(hit took saw liked))) 36 | (defn noun-phrase [] (concat (Article) (Adj*) (Noun) (PP*))) 37 | (defn verb-phrase [] (concat (Verb) (noun-phrase))) 38 | (defn sentence [] (concat (noun-phrase) (verb-phrase))) 39 | 40 | ;; Generate some sentences 41 | (dotimes [_ 10] 42 | (println (sentence))) 43 | -------------------------------------------------------------------------------- /src/paip/2_grammar/grammar_rule_based.clj: -------------------------------------------------------------------------------- 1 | ;;; Chapter 2: "A simple Lisp Program" - generative grammar - rule-based 2 | ;;; solution. 3 | ;;; 4 | ;;; Notes: 5 | ;;; - Using maps instead of a-lists for more idiomatic (and efficient) Clojure. 6 | ;;; Hence we also don't need the special -> notation. 7 | ;;; - simple-grammar, not *simple-grammar*. In Clojure everything is assumed to 8 | ;;; be constant unless stated otherwise, so earmuffs for constants are not 9 | ;;; necessary. 10 | 11 | (ns paip.2-grammar.grammar_rule_based) 12 | 13 | ;;; Maps category to a list of rewrites. When a rewrite is a list in itself, 14 | ;;; it's treated recursively. 15 | (def simple-grammar 16 | {'sentence '((noun-phrase verb-phrase)), 17 | 'noun-phrase '((Article Noun)), 18 | 'verb-phrase '((Verb noun-phrase)), 19 | 'Article '(the a), 20 | 'Noun '(man ball woman table), 21 | 'Verb '(hit took saw liked)}) 22 | 23 | (def grammar simple-grammar) 24 | 25 | (defn rewrites 26 | "Return a list of the possible rewrites for this category." 27 | [category] 28 | (get grammar category)) 29 | 30 | (defn generate 31 | "Generate a random sentence or phrase." 32 | [phrase] 33 | (cond (list? phrase) (mapcat generate phrase) 34 | (rewrites phrase) (generate (rand-nth (rewrites phrase))) 35 | :else (list phrase))) 36 | 37 | ;;; Solution to exercise 2.1 38 | (defn generate-2-1 39 | [phrase] 40 | ;; Uses if-let since a cond solution would require mean (rewrites phrase) 41 | ;; even when not necessary. In the book, setf is used on a let binding but 42 | ;; this goes against the Clojure way. We could define a cond-let macro 43 | ;; for Clojure too. 44 | (if-let [prewrites (rewrites phrase)] 45 | (generate (rand-nth prewrites)) 46 | (if (list? phrase) 47 | (mapcat generate phrase) 48 | (list phrase)))) 49 | 50 | ;;;---- To enable tracing, uncomment the following two lines 51 | ;(use 'clojure.tools.trace) 52 | ;(trace-vars generate rewrites) 53 | ;;;---- 54 | 55 | ;(dotimes [_ 10] 56 | ;(println (generate 'sentence))) 57 | 58 | (defn generate-tree 59 | "Generate a random sentence or phrase, with a complete parse tree." 60 | [phrase] 61 | (cond (list? phrase) (map generate-tree phrase) 62 | (rewrites phrase) 63 | (cons phrase (generate-tree (rand-nth (rewrites phrase)))) 64 | :else (list phrase))) 65 | 66 | ;(generate-tree 'sentence) 67 | 68 | (defn combine-all 69 | "Return a list of lists formed by appeding a y to an x. 70 | E.g. (combine-all '((a) (b)) '((1) (2))) 71 | -> ((a 1) (b 1) (a 2) (b 2))" 72 | [xlist ylist] 73 | (mapcat (fn [y] 74 | (map #(concat % y) xlist)) 75 | ylist)) 76 | 77 | (defn generate-all 78 | "Generate a list of all possible expansions of this phrase." 79 | [phrase] 80 | (cond (nil? phrase) (list nil) 81 | (list? phrase) (combine-all (generate-all (first phrase)) 82 | (generate-all (seq (rest phrase)))) 83 | (rewrites phrase) (mapcat generate-all (rewrites phrase)) 84 | :else (list (list phrase)))) 85 | 86 | (count (generate-all 'sentence)) 87 | -------------------------------------------------------------------------------- /src/paip/4_gps/gps_basic.clj: -------------------------------------------------------------------------------- 1 | ;;; Chapter 4: "GPS: The general problem solver" - the basic/naive solver. 2 | ;;; 3 | ;;; Notes: 4 | ;;; - This solution tries to be as faithful as possible to the original CL code, 5 | ;;; including a mutable state. Not using mutable state here would result in a 6 | ;;; completely different design, which is presented in the rest of the chapter 7 | ;;; anyway. 8 | ;;; - One difference is using add-set and del-set instead of add-list and 9 | ;;; del-list, because in Clojure set-like ops are efficiently defined on sets, 10 | ;;; not lists. 11 | 12 | (ns paip.4-gps.gps-basic) 13 | 14 | (defrecord Op [action preconds add-set del-set]) 15 | 16 | ;;; *ops* and *state* are dynamic variables that will be set by GPS when 17 | ;;; executing a solution to some particular problem. The funuctions defined here 18 | ;;; refer to these variables directly. 19 | (def ^:dynamic *ops* nil) 20 | (def ^:dynamic *state* nil) 21 | 22 | ;;; Pre-declare symbols used in the functions below. 23 | (declare achieve) 24 | 25 | (defn appropriate? 26 | "Is it appropriate to perform an operation to achieve a goal?" 27 | [goal op] 28 | (contains? (:add-set op) goal)) 29 | 30 | (defn apply-op 31 | "Print a message and update *state* if op is applicable and return true." 32 | [op] 33 | (when (every? achieve (:preconds op)) 34 | (println "Executing " (:action op)) 35 | ;; Do the update in a single swap! 36 | (swap! *state* #(clojure.set/union 37 | (clojure.set/difference % (:del-set op)) 38 | (:add-set op))) 39 | true)) 40 | 41 | (defn achieve 42 | "Try to achieve a goal and return true if it's achieved, false otherwise." 43 | [goal] 44 | (or (contains? @*state* goal) 45 | (some apply-op (filter #(appropriate? goal %) *ops*)))) 46 | 47 | (defn GPS 48 | "GPS: achieve all goals using ops, from the starting state 'state'." 49 | [state goals ops] 50 | ;; Set a dynamic binding for *ops* and *state* while the solution is 51 | ;; executing. 52 | (binding [*ops* ops 53 | *state* state] 54 | (when (every? achieve goals) 55 | 'solved))) 56 | 57 | ;;; Data for the 'drive child to school' problem. State entities and actions are 58 | ;;; symbols. 59 | (def school-ops 60 | "A list of available operators" 61 | (list (map->Op {:action 'drive-son-to-school 62 | :preconds '(son-at-home car-works) 63 | :add-set #{'son-at-school} 64 | :del-set #{'son-at-home}}) 65 | (map->Op {:action 'shop-installs-battery 66 | :preconds '(car-needs-battery shop-knows-problem shop-has-money) 67 | :add-set #{'car-works}}) 68 | (map->Op {:action 'tell-shop-problem 69 | :preconds '(in-communication-with-shop) 70 | :add-set #{'shop-knows-problem}}) 71 | (map->Op {:action 'telephone-shop 72 | :preconds '(know-phone-number) 73 | :add-set #{'in-communication-with-shop}}) 74 | (map->Op {:action 'look-up-number 75 | :preconds '(have-phone-book) 76 | :add-set #{'know-phone-number}}) 77 | (map->Op {:action 'give-shop-money 78 | :preconds '(have-money) 79 | :add-set #{'shop-has-money} 80 | :del-set #{'have-money}}))) 81 | 82 | (GPS 83 | (atom #{'son-at-home 'car-needs-battery 'have-money 'have-phone-book}) 84 | '(son-at-school) school-ops) 85 | 86 | (GPS 87 | (atom #{'son-at-home 'car-needs-battery 'have-money}) 88 | '(son-at-school) school-ops) 89 | 90 | (GPS 91 | (atom #{'son-at-home 'car-works}) 92 | '(son-at-school) school-ops) 93 | 94 | ;(clojure.pprint/pprint school-ops) 95 | -------------------------------------------------------------------------------- /src/paip/6_tools/pat_match.clj: -------------------------------------------------------------------------------- 1 | ;;; Chapter 6, section 6.2: a pattern matching tool. 2 | 3 | (ns paip.6-tools.pat-match 4 | (:use paip.utils)) 5 | 6 | (def fail 7 | "Denotes a failure in matching" 8 | nil) 9 | 10 | (def no-bindings 11 | "Denotes successful match with no variable bindings" 12 | {}) 13 | 14 | (defn variable? 15 | [x] 16 | (and (symbol? x) (= \? (get (str x) 0)))) 17 | 18 | (defn get-binding 19 | "Find a variable->value binding in the given binding." 20 | [v bindings] 21 | (get bindings v)) 22 | 23 | (defn extend-bindings 24 | "Add a v -> value mappping to bindings." 25 | [v value bindings] 26 | (assoc bindings v value)) 27 | 28 | (defn match-variable 29 | "Does v match input? Uses (or updates) and returns bindings." 30 | [v input bindings] 31 | (let [b (get-binding v bindings)] 32 | (cond (nil? b) (extend-bindings v input bindings) 33 | (= input b) bindings 34 | :else fail))) 35 | 36 | (declare pat-match) 37 | 38 | (defn match-is 39 | "Suceed and bind var if the input satisfied pred. 40 | var-and-pred is the list (var pred)." 41 | [var-and-pred input bindings] 42 | (let [[v pred] var-and-pred 43 | new-bindings (pat-match v input bindings)] 44 | (if (or (= new-bindings fail) 45 | (not ((resolve pred) input))) 46 | fail 47 | new-bindings))) 48 | 49 | (defn match-and 50 | "Succeed if all the patterns match the input." 51 | [patterns input bindings] 52 | (cond (= bindings fail) fail 53 | (empty? patterns) bindings 54 | :else (match-and 55 | (rest patterns) 56 | input 57 | (pat-match (first patterns) input bindings)))) 58 | 59 | (defn match-or 60 | "Succeed if any of the patterns match the input." 61 | [patterns input bindings] 62 | (if (empty? patterns) 63 | fail 64 | (let [new-bindings (pat-match (first patterns) input bindings)] 65 | (if (= new-bindings fail) 66 | (match-or (rest patterns) input bindings) 67 | new-bindings)))) 68 | 69 | (defn match-not 70 | "Succeed if none of the patterns match the input. 71 | This will never bind variables." 72 | [patterns input bindings] 73 | (if (match-or patterns input bindings) 74 | fail 75 | bindings)) 76 | 77 | (def single-matcher-table 78 | "Table mapping single matcher names to matching functions." 79 | {'?is match-is 80 | '?or match-or 81 | '?and match-and 82 | '?not match-not}) 83 | 84 | (defn first-match-pos 85 | "Find the first position that pat1 could possibly match input, starting 86 | at position start. If pat1 is non-constant, then just return start, 87 | conservatively assuming it could match." 88 | [pat1 input start] 89 | (cond (and (not (list? pat1)) 90 | (not (variable? pat1))) (let [idx (index-in-seq input pat1 start)] 91 | (if (< idx 0) 92 | nil 93 | idx)) 94 | (< start (count input)) start 95 | :else nil)) 96 | 97 | (defn segment-match-* 98 | "Match the segment pattern ((?* ?var) . pat) against input. The optional start 99 | parameter specifices where to start matching (index in input) the pattern 100 | after the current ?* match." 101 | ([pattern input bindings] (segment-match-* pattern input bindings 0)) 102 | ([pattern input bindings start] 103 | (let [v (second (first pattern)) 104 | pat (rest pattern)] 105 | (if (nil? pat) 106 | ;; If there's no more pat to match, this is a simple variable match of 107 | ;; ?var on the whole input. 108 | (match-variable v input bindings) 109 | ;; Otherwise, find the first position in the input where pat could match. 110 | ;; Try to match our segment until there pat from there. If this fails, 111 | ;; rerun with start+1 to try matching at the next position. 112 | (let [pos (first-match-pos (first pat) input start)] 113 | (if (nil? pos) 114 | fail 115 | (let [b2 (pat-match pat 116 | (nthrest input pos) 117 | (match-variable v 118 | (take pos input) 119 | bindings))] 120 | ;; If this match failed, try another longer one 121 | (if (= b2 fail) 122 | (segment-match-* pattern input bindings (+ pos 1)) 123 | b2)))))))) 124 | 125 | (defn segment-match-+ 126 | "Match ?+ -- one or more elements of input." 127 | [pattern input bindings] 128 | (segment-match-* pattern input bindings 1)) 129 | 130 | (defn segment-match-? 131 | "Match ?? -- zero or one elements of input." 132 | [pattern input bindings] 133 | (let [v (second (first pattern)) 134 | pat (rest pattern)] 135 | (or (pat-match (conj pat v) input bindings) 136 | (pat-match pat input bindings)))) 137 | 138 | (def segment-matcher-table 139 | "Table mapping segment matcher names to matching functions." 140 | {'?* segment-match-* 141 | '?+ segment-match-+ 142 | '?? segment-match-?}) 143 | 144 | (defn single-pattern? 145 | "Is this a single-matching pattern?" 146 | [pattern] 147 | (and (list? pattern) (get single-matcher-table (first pattern)))) 148 | 149 | (defn single-matcher 150 | "Call the right single-pattern matching function." 151 | [pattern input bindings] 152 | ((get single-matcher-table (first pattern)) (rest pattern) input bindings)) 153 | 154 | (defn segment-pattern? 155 | "Is this a segment-matching pattern?" 156 | [pattern] 157 | (and (list? pattern) 158 | (list? (first pattern)) 159 | (symbol? (first (first pattern))) 160 | (get segment-matcher-table (first (first pattern))))) 161 | 162 | (defn segment-matcher 163 | "Call the right function for this kind of segment pattern." 164 | [pattern input bindings] 165 | ((get segment-matcher-table (first (first pattern))) pattern input bindings)) 166 | 167 | (defn pat-match 168 | ([pattern input] (pat-match pattern input no-bindings)) 169 | ([pattern input bindings] 170 | (cond (= bindings fail) fail 171 | (variable? pattern) (match-variable pattern input bindings) 172 | (= pattern input) bindings 173 | (single-pattern? pattern) (single-matcher pattern input bindings) 174 | (segment-pattern? pattern) (segment-matcher pattern input bindings) 175 | (and (list? pattern) (list? input)) (pat-match 176 | (rest pattern) 177 | (rest input) 178 | (pat-match 179 | (first pattern) 180 | (first input) 181 | bindings)) 182 | :else fail))) 183 | -------------------------------------------------------------------------------- /src/paip/6_tools/pat_match_test.clj: -------------------------------------------------------------------------------- 1 | (ns paip.6-tools.pat-match-test 2 | (:use clojure.test) 3 | (:use paip.6-tools.pat-match)) 4 | 5 | (deftest pat-match-basic-var-test 6 | (is (= {'?v 'a} (pat-match '(?v) '(a)))) 7 | 8 | (is (= {'?v 'c} (pat-match '(a ?v b) '(a c b)))) 9 | (is (nil? (pat-match '(a ?v b) '(a c d)))) 10 | 11 | (is (= {'?v 'b} (pat-match '(a t ?v) '(a t b)))) 12 | 13 | (is (= {'?v 'b, '?u 'a} (pat-match '(?u t ?v) '(a t b)))) 14 | 15 | (is (= {'?v 'b} (pat-match '(?v t ?v) '(b t b)))) 16 | (is (nil? (pat-match '(?v t ?v) '(a t b)))) 17 | 18 | (is (= {'?v '(t k)} (pat-match '(a ?v) '(a (t k)))))) 19 | 20 | (deftest pat-match-is-test 21 | (is (= {'?v 8} (pat-match '(a = (?is ?v number?)) '(a = 8)))) 22 | (is (nil? (pat-match '(a = (?is ?v number?)) '(a = jk))))) 23 | 24 | (deftest pat-match-and-test 25 | (is (nil? (pat-match '(a (?and (?is ?v number?) (?is ?v odd?))) '(a 8)))) 26 | (is (= {'?v 7} (pat-match '(a (?and (?is ?v number?) (?is ?v odd?))) '(a 7))))) 27 | 28 | (deftest pat-match-or-test 29 | (is (= {'?v 7} (pat-match '(a (?or (?is ?v odd?) (?is ?v zero?))) '(a 7)))) 30 | (is (= {'?v 0} (pat-match '(a (?or (?is ?v odd?) (?is ?v zero?))) '(a 0)))) 31 | (is (nil? (pat-match '(a (?or (?is ?v odd?) (?is ?v zero?))) '(a 2))))) 32 | 33 | (deftest pat-match-not-test 34 | (is (= {'?v 7} (pat-match '(a ?v (?not ?v)) '(a 7 8)))) 35 | (is (nil? (pat-match '(a ?v (?not ?v)) '(a 7 7))))) 36 | 37 | (deftest pat-match-*-test 38 | (is (= {'?v '(b c)} (pat-match '(a (?* ?v) d) '(a b c d)))) 39 | (is (= {'?v '(b)} (pat-match '(a (?* ?v) d) '(a b d)))) 40 | (is (= {'?v '()} (pat-match '(a (?* ?v) d) '(a d)))) 41 | 42 | (is (= {'?v '(b c)} (pat-match '(a (?* ?v) d ?v) '(a b c d (b c))))) 43 | (is (nil? (pat-match '(a (?* ?v) b c d ?v) '(a b c d (b k))))) 44 | (is (= {'?v '()} (pat-match '(a (?* ?v) b c d ?v) '(a b c d ())))) 45 | 46 | (is (= {'?x '(), '?y '(b c)} (pat-match '(a (?* ?x) (?* ?y) d) '(a b c d)))) 47 | (is (= {'?x '(b c), '?y '(d)} (pat-match '(a (?* ?x) (?* ?y) ?x ?y) 48 | '(a b c d (b c) (d)))))) 49 | 50 | (deftest pat-match-+-test 51 | ;; ?+ can't match empty lists 52 | (is (nil? (pat-match '(a (?+ ?v) d) '(a d)))) 53 | 54 | (is (= {'?v '(t)} (pat-match '(a (?+ ?v) d) '(a t d)))) 55 | (is (= {'?v '(t), '?u '(p a)} (pat-match '(a (?+ ?v) (?+ ?u) d) '(a t p a d))))) 56 | 57 | (deftest pat-match-?-test 58 | (is (= {'?v 'k} (pat-match '((?? ?v) d) '(k d) {}))) 59 | (is (= {} (pat-match '((?? ?v) d) '(d) {}))) 60 | 61 | (is (nil? (pat-match '(g (?? ?v) d) '(d) {}))) 62 | (is (= {} (pat-match '(g (?? ?v) d) '(g d) {}))) 63 | (is (= {'?v 'i} (pat-match '(g (?? ?v) d) '(g i d) {}))) 64 | (is (nil? (pat-match '(g (?? ?v) d) '(g i x d) {})))) 65 | 66 | (run-tests) 67 | -------------------------------------------------------------------------------- /src/paip/6_tools/search.clj: -------------------------------------------------------------------------------- 1 | ;;; Chapter 6, section 6.4: a set of searching tools 2 | 3 | (ns paip.6-tools.search 4 | (:use paip.utils)) 5 | 6 | (defn tree-search 7 | "Finds a state that satisfies goal?-fn; Starts with states, and searches 8 | according to successors and combiner. If successful, returns the state; 9 | otherwise returns nil." 10 | [states goal?-fn successors combiner] 11 | (printfv ";; Search: %s%n" (pr-str states)) 12 | (cond (empty? states) nil 13 | (goal?-fn (first states)) (first states) 14 | :else (tree-search (combiner (successors (first states)) 15 | (rest states)) 16 | goal?-fn 17 | successors 18 | combiner))) 19 | 20 | (defn depth-first-search 21 | "Search new states first until goal is reached." 22 | [start goal?-fn successors] 23 | (tree-search (list start) goal?-fn successors concat)) 24 | 25 | (defn prepend 26 | [x y] 27 | (concat y x)) 28 | 29 | (defn breadth-first-search 30 | "Search old states first until goal is reached." 31 | [start goal?-fn successors] 32 | (tree-search (list start) goal?-fn successors prepend)) 33 | 34 | (defn binary-tree 35 | "A successors function representing a binary tree." 36 | [x] 37 | (list (* 2 x) (+ 1 (* 2 x)))) 38 | 39 | (defn finite-binary-tree 40 | "Returns a successor function that generates a binary tree with n nodes." 41 | [n] 42 | (fn [x] 43 | (filter #(<= % n) (binary-tree x)))) 44 | 45 | ;(with-verbose 46 | ;(depth-first-search 1 #(= % 32) binary-tree)) 47 | 48 | ;(with-verbose 49 | ;(depth-first-search 1 #(= % 22) (finite-binary-tree 15))) 50 | 51 | ;(with-verbose 52 | ;(breadth-first-search 1 #(= % 32) binary-tree)) 53 | 54 | (defn diff 55 | "Given n, returns a function that computes the distance of its argument from 56 | n." 57 | [n] 58 | (fn [x] (Math/abs (- x n)))) 59 | 60 | (defn sorter 61 | "Returns a combiner function that sorts according to cost-fn." 62 | [cost-fn] 63 | (fn [new old] 64 | (sort-by cost-fn (concat new old)))) 65 | 66 | (defn best-first-search 67 | "Search lowest cost states first until goal is reached." 68 | [start goal?-fn successors cost-fn] 69 | (tree-search (list start) goal?-fn successors (sorter cost-fn))) 70 | 71 | ;(with-verbose 72 | ;(best-first-search 1 #(= % 12) binary-tree (diff 12))) 73 | 74 | (defn beam-search 75 | "Search highest scoring states first until goal is reached, but never consider 76 | more than beam-width states at a time." 77 | [start goal?-fn successors cost-fn beam-width] 78 | (tree-search (list start) goal?-fn successors 79 | (fn [old new] 80 | (let [sorted ((sorter cost-fn) old new)] 81 | (take beam-width sorted))))) 82 | 83 | ;; With beam-width of 2 won't be found... 84 | ;(with-verbose 85 | ;(beam-search 1 #(= % 12) binary-tree (diff 12) 3)) 86 | -------------------------------------------------------------------------------- /src/paip/6_tools/search_test.clj: -------------------------------------------------------------------------------- 1 | ; To run these tests from the lein REPL, run: 2 | ; => (require '[paip.6-tools.search-test] :reload-all) 3 | ; 4 | ; and the tests will run because of the (run-tests) invocation at the bottom. 5 | (ns paip.6-tools.search-test 6 | (:use clojure.test) 7 | (:use paip.6-tools.search)) 8 | 9 | (deftest depth-first-search-test 10 | (is (= 12 (depth-first-search 1 #(= % 12) (finite-binary-tree 15)))) 11 | (is (nil? (depth-first-search 1 #(= % 22) (finite-binary-tree 15))))) 12 | 13 | (deftest breadth-first-search-test 14 | (is (= 12 (breadth-first-search 1 #(= % 12) (finite-binary-tree 15)))) 15 | (is (nil? (breadth-first-search 1 #(= % 22) (finite-binary-tree 15))))) 16 | 17 | (deftest best-first-search-test 18 | (is (= 12 (best-first-search 1 #(= % 12) (finite-binary-tree 15) (diff 12)))) 19 | (is (nil? (best-first-search 1 #(= % 22) (finite-binary-tree 15) (diff 12))))) 20 | 21 | (deftest sorter-test 22 | (is (= '(8 5 10 20 124 9991) 23 | ((sorter #(count (str %))) '(10 8 5) '(124 20 9991))))) 24 | 25 | (deftest beam-search-test 26 | (is (nil? (beam-search 1 #(= % 12) (finite-binary-tree 15) (diff 12) 2))) 27 | (is (= 12 (beam-search 1 #(= % 12) (finite-binary-tree 15) (diff 12) 3)))) 28 | 29 | (run-tests) 30 | -------------------------------------------------------------------------------- /src/paip/core.clj: -------------------------------------------------------------------------------- 1 | (ns paip.core 2 | (:gen-class)) 3 | 4 | (defn -main 5 | "I don't do a whole lot ... yet." 6 | [& args] 7 | (println "Hello, World!")) 8 | -------------------------------------------------------------------------------- /src/paip/utils.clj: -------------------------------------------------------------------------------- 1 | ;;; Foundational utility functions useful for translating for Common Lisp, but 2 | ;;; that don't have trivial translations in Clojure. 3 | (ns paip.utils) 4 | 5 | (defn index-in-seq 6 | "Finds the index of item in the given sequence; the optional start parameter 7 | specifies the starting index to start looking from. Returns -1 when not found. 8 | Note that this is using a generic approach unoptimized for vectors." 9 | ([seq item start] 10 | (let [idx (.indexOf (nthrest seq start) item)] 11 | (if (>= idx 0) 12 | (+ start idx) 13 | -1))) 14 | ([seq item] (.indexOf seq item))) 15 | 16 | (defn cons? 17 | "Is x a 'cons cell'? Non empty lists return true, otherwise false. 18 | 19 | Doesn't raise an exception on non-sequence inputs. 20 | See http://eli.thegreenplace.net/2016/common-lisps-consp-and-listp-in-clojure/ 21 | for more details." 22 | [x] 23 | (and (list? x) (not (empty? x)))) 24 | 25 | ;;; printv variant that prints only when the dynamic *verbose* is true. A 26 | ;;; convenience wrapper macro with-verbose is provided. 27 | (def ^:dynamic *verbose* false) 28 | 29 | (defmacro printfv 30 | [fmt & args] 31 | `(when *verbose* 32 | (printf ~fmt ~@args))) 33 | 34 | (defmacro with-verbose 35 | [& body] 36 | `(binding [*verbose* true] ~@body)) 37 | -------------------------------------------------------------------------------- /src/paip/utils_test.clj: -------------------------------------------------------------------------------- 1 | (ns paip.utils-test 2 | (:use clojure.test) 3 | (:use paip.utils)) 4 | 5 | (deftest index-in-seq-test 6 | (is (= 0 (index-in-seq '(10 20 30) 10))) 7 | (is (= 1 (index-in-seq '(10 20 30) 20))) 8 | (is (= 2 (index-in-seq '(10 20 30) 30))) 9 | (is (= -1 (index-in-seq '(10 20 30) 50))) 10 | 11 | (is (= 1 (index-in-seq '(10 20 30 20) 20))) 12 | (is (= 3 (index-in-seq '(10 20 30 20) 20 2))) 13 | (is (= -1 (index-in-seq '(10 20 30 20 50) 20 4))) 14 | 15 | (is (= -1 (index-in-seq [10 20 30 20] 22))) 16 | (is (= 1 (index-in-seq [10 20 30 20] 20))) 17 | (is (= 3 (index-in-seq [10 20 30 20] 20 2))) 18 | 19 | (is (= -1 (index-in-seq '() 10)))) 20 | 21 | (deftest cons?-test 22 | (is (cons? '(1))) 23 | (is (cons? '(1 2))) 24 | (is (cons? '(1 2 3))) 25 | 26 | (is (not (cons? [1 2 3]))) 27 | (is (not (cons? '()))) 28 | (is (not (cons? nil))) 29 | (is (not (cons? 'sdf)))) 30 | 31 | (run-tests) 32 | --------------------------------------------------------------------------------