Хорошо, во-первых, уже 1970 год, и мы изобрели понятие использования абстракции. Вместо кода, который заполнен car
, cdr
& cons
, мы можем использовать значимые имена для наших объектов: мы будем иметь дело с объектами, называемыми node , где каждый узел является либо дерево или лист . У дерева есть имя и список ветвей (списки ветвей - это списки, абстракция там не нужна), листья не имеют определенной структуры: они просто не деревья.
(defun node-tree-p (o)
(consp o))
(defun node-leaf-p (o)
(not (node-tree-p o)))
(defun tree-name (tree)
(car tree))
(defun tree-branches (tree)
(cdr tree))
(defun make-tree (name branches)
(cons name branches))
Я собираюсь представлять имена объединенных деревьев в виде списков (например, они являются списками , поэтому можно использовать функции списков для них, нам не нужно их абстрагировать). Таким образом, нам понадобится функция для объединения имен, которая при этом оборачивает беспристрастность в зависимости от того, является ли вы уже списочным именем:
(defun coalesce-names (n1 n2)
(append (if (listp n1) n1 (list n1))
(if (listp n2) n2 (list n2))))
Итак, теперь мы можем написать функцию, которая обходит дерево и объединяет что является объединяемым:
(defun maybe-coalesce-node (node)
(if (node-tree-p node)
;; it's a tree, which is a candidate
(if (= (length (tree-branches node)) 1)
;; it's got one branch: it's a good candidate
(let ((branch (first (tree-branches node))))
(if (node-tree-p branch)
;; the branch is a tree: this is coalescable: coalesce
;; it and then recurse on the result
(maybe-coalesce-node (make-tree (coalesce-names (tree-name node)
(tree-name branch))
(tree-branches branch)))
;; the branch is a leaf: this is not coalescable
node))
;; it's a tree, but it has more than one branch, so make a
;; tree whose branches have been coalesced
(make-tree (tree-name node)
(mapcar #'maybe-coalesce-node (tree-branches node))))
;; it's a leaf, which is not a candidate
node))
Обратите внимание, что это функция : она принимает узел в качестве аргумента и возвращает узел, который может быть тем же узлом, но он не изменяет узел.
А теперь:
> (maybe-coalesce-node
'(a (b 1) (c (d (e 2))) (f (g (h 3) (i 4)))))
(a (b 1) ((c d e) 2) ((f g) (h 3) (i 4)))
Итак, результатом этого является то, что мы можем объединить деревья, чтобы создать деревья, имена которых являются списками имен. Теперь мы хотим превратить эти имена в строки. Чтобы сделать это, давайте напишем общую функцию отображения дерева, которая отобразит функцию на узле:
(defun map-node (f node)
;; map F over the nodes in TOP-NODE. F should return a node, but it
;; may have a different structure than its argument.
(let ((new-node (funcall f node)))
(if (node-tree-p new-node)
(make-tree (tree-name new-node)
(mapcar #'(lambda (n)
(map-node f n))
(tree-branches new-node)))
new-node)))
А теперь давайте напишем функцию, которая переписывает имя дерева, используя вспомогательную функцию для выполнения работа:
(defun stringify-tree-name (name)
(format nil "~{~A~^+~}" (if (listp name) name (list name))))
(defun maybe-rewrite-node-name (node &key (name-rewriter #'stringify-tree-name))
(if (node-tree-p node)
(make-tree (funcall name-rewriter (tree-name node))
(tree-branches node))
node))
И теперь мы можем объединить и переписать имена узлов:
> (map-node #'maybe-rewrite-node-name
(maybe-coalesce-node
'(a (b 1) (c (d (e 2))) (f (g (h 3) (i 4))))))
("a" ("b" 1) ("c+d+e" 2) ("f+g" ("h" 3) ("i" 4)))
В качестве упражнения: переписать maybe-coalesce-node
в терминах map-node
.