;;; An L-System implementation in clojure ;;; see: http://en.wikipedia.org/wiki/L-system (ns de.clojure-buch.lsystem (:require [clojure.test :as test])) ;; the string rewrite functionality (defn tokenize-str "Convert a string into a sequence of keywords." [s] (map #(keyword (str %)) s)) (defn process-rules "Convenience for production rules." [r] (into {} (map #(hash-map (keyword (first %)) (tokenize-str (second %))) r))) (defn lsystem-seq "String rewrite-fn as a lazy seq." [word rules] (let [t-seq (tokenize-str word) rules (process-rules rules)] (iterate (fn [in-seq] (mapcat #(if-let [r (rules %)] r [%]) in-seq)) t-seq))) ;; Several l-systems (def algae (lsystem-seq "A" {"A" "AB" "B" "A"})) (def cantor (lsystem-seq "A" {"A" "ABA" "B" "BBB"})) ; test-function body as a macro (defmacro iterate-and-check [res s] `(loop [res# ~res s# ~s] (if (first res#) (do (test/is (= (tokenize-str (first res#)) (first s#))) (recur (next res#) (next s#)))))) ;; tests for lsystem sequence (test/deftest algae-lsystem (let [res ["A" "AB" "ABA" "ABAAB" "ABAABABA" "ABAABABAABAAB" "ABAABABAABAABABAABABA" "ABAABABAABAABABAABABAABAABABAABAAB"] s algae] (iterate-and-check res s))) ; note: incorrect result to demonstrate test failure (test/deftest cantor-lsystem-failing (let [res ["A" "ABAX" "ABABBBABA" "ABABBBABABBBBBBBBBABABBBABA"] s cantor] (iterate-and-check res s)))