Lindenmayer-Systeme

Ein Lindenmayer-System wird durch vier Angaben beschrieben:

- Zeichen, die Konstanten sind,
- Zeichen, die für Variablen stehen,
- ein Startwort, das aus Variablen und Konstanten gebildet werden kann und
- mehrere Ersetzungsregeln, von denen jede ein Wort auf ein anderes abbildet.

Man interessiert sich dafür, welche Wörter aus dem Startwort durch mehrmaliges Anwenden der Ersetzungsregeln entstehen.

Wenn die Prämisse jeder Ersetzungsregel aus nur einem Variablenzeichen besteht, dann ist das Lindenmayer-System kontextfrei. Das führt zu einer einfachen Realisierung.

(defclass lindenmayer ())

(defmethod initialize ((this lindenmayer) constants variables start rules)
  (and
    (every? symbol? constants)
    (every? symbol? variables)
    (every? symbol? start)
    (disjoint? constants variables)
    (let
      ((alphabet (union constants variables)))
      (and
        (null? (set-difference start alphabet))
        (every?
          (lambda (rule)
            (and
              (single? (first rule))
              (member? (first (first rule)) variables)
              (null? (set-difference (second rule) alphabet))))
          rules))))
  (progn
    (.= this start start)
    (.= this compiled-rules (compile-rules rules))
    (freeze this)))

(defproc compile-rules (rules)
  (let
    ((table (make-hash-table)))
    (dolist (rule rules (freeze table))
      (put-hash-table-value table
        (first (first rule))
        (second rule)))))

Der Konstruktor erwartet vier Argumente - analog zu den oben beschriebenen vier Angaben. Er überprüft, ob die Angaben ein kontextfreies Lindenmayer-System spezifizieren. Die vom Konstruktor verwendete Methode compile-rules fügt die Regeln in eine Hashtabelle ein.

Die Methode apply-rules wendet die Regeln auf ein Wort an. Dazu wird für jedes Zeichen im Wort in der Hashtabelle nachgeschlagen, ob eine Ersetzung dafür vorgesehen ist. Wenn ja, dann geht diese in das Resultat ein. Anderenfalls wird das Zeichen unverändert übernommen.

(defmethod apply-rules ((this lindenmayer) word)
  t
  (with-slots-read-only (compiled-rules) this
    (apply append
      (map-with
        (lambda (element)
          (aif
            (get-hash-table-value compiled-rules element)
            it
            (list element)))
        word))))

Die Methode get-expansion liefert die count-fache Anwendung der Ersetzungsregeln auf das Startwort.
 
(defmethod get-expansion ((this lindenmayer) count)
  t
  (let
    ((word (. this start)))
    (dotimes (index count word)
      (setq word (apply-rules this word)))))


lindenmayer.sheet.xml