Yesterday I wrote a block of scala code to calculate the (integer) Loday coordinates of the convex polytopes that model associahedra. I wrote the code with the help of a local LLM (codellama) but I wasn’t really happy about it. The code was bloated, had unnecessary bits, and not precise at all. Today, I am going to rewrite it in Common Lisp. This is completely unadulterated.
Let us start with our ground function that generates all parenthesizations of \(n+1\)-terms. This is almost verbatim translation of the scala code, but then again, this is the only sensible solution.
defun parenthesizations (n)
(if (= n 1)
(list 0)
(loop for i from 1 below n
(append (mapcar #'cons
(parenthesizations i) - n i))))))
(parenthesizations (
4) (parenthesizations
PARENTHESIZATIONS0 0 0 . 0) (0 (0 . 0) . 0) ((0 . 0) 0 . 0) ((0 0 . 0) . 0)
((0 . 0) . 0) . 0)) (((
Now, both of the functions I wrote yesterday for labeling and counting leaves can be combined into a single function that traverses over a tree:
defun traverse (tree base fn)
(if (atom tree)
(funcall base tree)
(funcall fn (traverse (car tree) base fn)
(cdr tree) base fn)))) (traverse (
TRAVERSE
For counting we do
0 (0 . 0) . 0) (lambda (x) 1) #'+) (traverse '(
4
For flattening we do
0 (0 . 0) . 0) #'list #'append) (traverse '(
0 0 0 0) (
For relabelling we do
mapcar (lambda (tree)
(let ((counter (let ((n 0)) (lambda (x) (incf n)))))
(#'cons)))
(traverse tree counter 4)) (parenthesizations
1 2 3 . 4) (1 (2 . 3) . 4) ((1 . 2) 3 . 4) ((1 2 . 3) . 4)
((1 . 2) . 3) . 4)) (((
Next, I’ll implement the function that returns the size of the minimal subtree that contains a collection of elements. This is slight generalization of yesterday’s code where the code checked only a pair:
defun min-subtree (tree pair)
(let ((elts (traverse tree #'list #'append)))
(if (atom tree)
(most-positive-fixnum
let ((left (min-subtree (car tree) pair))
(cdr tree) pair)))
(right (min-subtree (if (subsetp pair elts)
(min left right (1- (length elts)))
(min left right))))))
(
1 . 2) ((3 . 4) . 5) . 6) '(1 2 3)) (min-subtree '((
MIN-SUBTREE5
Next, the Loday coordinates for a given tree:
defun loday-coordinates (tree n)
(loop for i from 1 below n
(list i (1+ i)))))
collect (min-subtree tree (
1 . 2) ((3 . 4) . 5) . 6) 6) (loday-coordinates '((
LODAY-COORDINATES1 5 1 2 3) (
And finally, the coordinates of the extremal points for the convex polytope that models an associahedron \(K_n\):
defun associahedra-coordinates (n)
(labels ((counter ()
(let ((n 0)) (lambda (x) (incf n))))
(
(label-tree (tree)let ((c (counter)))
(#'cons))))
(traverse tree c mapcar (lambda (x) (loday-coordinates (label-tree x) n))
(
(parenthesizations n))))
4)
(associahedra-coordinates 5) (associahedra-coordinates
ASSOCIAHEDRA-COORDINATES3 2 1) (3 1 2) (1 3 1) (2 1 3) (1 2 3))
((4 3 2 1) (4 3 1 2) (4 1 3 1) (4 2 1 3) (4 1 2 3) (1 4 2 1) (1 4 1 2)
((2 1 4 1) (1 2 4 1) (3 2 1 4) (3 1 2 4) (1 3 1 4) (2 1 3 4) (1 2 3 4)) (
In my experience of playing with LLMs (both local and hosted) in
generating code, LLMs do well with imperative languages in relatively
simple problems. For some reason, all of the LLMs I tried generated junk
lisp code for the problems I am interested in. I tend to write
recursive, generalized/abstracted lisp code (think of
count-leaves
or flatten
or
label-leaves
below and traverse
function
above). LLMs can’t get even close to write something similar.
defun count-leaves (tree)
(if (atom tree)
(1
+ (count-leaves (car tree))
(cdr tree)))))
(count-leaves (
1 . 2) ((3 . 4) . 5) . 6))
(count-leaves '((
defun flatten (tree)
(if (atom tree)
(list tree)
(append (flatten (car tree))
(cdr tree)))))
(flatten (
1 . 2) ((3 . 4) . 5) . 6))
(flatten '((
defun label-leaves (tree counter)
(if (atom tree)
(funcall counter)
(cons (label-leaves (car tree) counter)
(cdr tree) counter))))
(label-leaves (
let ((c (let ((n 0)) (lambda () (incf n)))))
(0 . 0) ((0 . 0) . 0) . 0) c)) (label-leaves '((
COUNT-LEAVES6
FLATTEN1 2 3 4 5 6)
(
LABEL-LEAVES1 . 2) ((3 . 4) . 5) . 6) ((