How to convert a flat list into a nested tree-like structure?

≯℡__Kan透↙ 提交于 2019-12-24 05:12:05

问题


How to convert a flat list into an arbitrarily complex tree-like structure? First, a simple example, convert '(1 2 3 4) into '(1 (2 (3 (4)))). I know how to do it with classical recursion:

(defun nestify (xs)
  (if (null xs)
      (list)
    (list (car xs) (nestify (cdr xs)))))

Now, what if the nested structure is arbitrarily complex? For example, I want to convert '(1 2 3 4 5 6 7 8) into '(1 (2 3) (4 (5 6) 7) 8). How can I write a general function that is able to convert a flat list in any such nested structure? I can think of giving a template with dummy values. For example:

* (nestify '(1 2 3 4 5 6 7 8) '(t (t t) (t (t t) t) t))
'(1 (2 3) (4 (5 6) 7) 8)

My first attempt using recursion and custom tree size finding function:

(defun length* (tr)
  "Count number of elements in a tree."
  (cond ((null tr) 0)
        ((atom tr) 1)
        (t (+ (length* (car tr))
              (length* (cdr tr))))))

(defun tree-substitute (xs tpl)
  "(tree-substitute '(1 2 3) '(t (t) t)) -> '(1 (2) 3)"
  (cond ((null tpl) nil)
        ((atom (car tpl))
         (cons (car xs) (tree (cdr xs) (cdr tpl))))
        (t (cons (tree xs (car tpl))
              (tree (nthcdr (length* (car tpl)) xs) (cdr tpl))))))

Is there any way to do this better, in a more elegant and concise way? For example, the function converting a list into a tree might not use the template, although I can't think of the method. Can I abstract away recursion and other details and have a neat reduce or some other high-level function?


回答1:


Turning (1 2 3 4) into (1 (2 (3 (4)))) actually isn't quite as simple as you might hope, if you're using reduce. You need to specify :from-end t if you want to process 4 first, and the reduction function is either called with 3 and 4, if no :initial-value is specified, or with 4 and the initial value, if one is. That means you can use something like this, where the function checks for the special initial case:

(reduce (lambda (x y)
          (if y
            (list x y)
            (list x)))
        '(1 2 3 4)
        :from-end t
        :initial-value nil)
;=> (1 (2 (3 (4))))

A solution that involves a template is much more interesting, in my opinion. It's easy enough to define a maptree function that maps a function over a tree and returns a new tree with the function results:

(defun maptree (function tree)
  "Return a tree with the same structure as TREE, but
whose elements are the result of calling FUNCTION with
the element from TREE.  Because TREE is treated as an 
arbitrarily nested structure, any occurrence of NIL is 
treated as an empty tree."
  (cond 
    ((null tree) tree)
    ((atom tree) (funcall function tree))
    ((cons (maptree function (car tree))
           (maptree function (cdr tree))))))
(maptree '1+ '(1 2 (3 (4 5)) (6 7)))
;=> (2 3 (4 (5 6)) (7 8))

Given the maptree function, it's not hard to call it with a function that provides an element from a list of elements, until that list of element is exhausted. This provides a definition of substitute-into:

(defun substitute-into (items tree)
  "Return a tree like TREE, but in which the elements
of TREE are replaced with elements drawn from ITEMS.  
If there are more elements in TREE than there are in 
ITEMS, the original elements of TREE remain in the result,
but a new tree structure is still constructed."
  (maptree #'(lambda (x)
               (if (endp items) x
                   (pop items)))
           tree))
(substitute-into '(1 2 3 4 5) '(t (u (v)) (w x)))
;=> (1 (2 (3)) (4 5))

(substitute-into '(1 2 3 4 5) '(t u (v w x) y z))
;=> (1 2 (3 4 5) Y Z)

See Also

The maptree above is actually just a special case of a more general reduce, or fold, function for trees. Have a look at Using reduce over a tree in Lisp for some more information about how you can fold over trees. In this case, you could use my tree-reduce function from my answer to that question:

(defun tree-reduce (node-fn leaf-fn tree)
  (if (consp tree)
      (funcall node-fn 
               (tree-reduce node-fn leaf-fn (car tree))
               (tree-reduce node-fn leaf-fn (cdr tree)))
      (funcall leaf-fn 
               tree)))

and define maptree in terms of it:

(defun maptree (function tree)
  (tree-reduce 'cons function tree))



回答2:


My attempt:

(defun mimicry (source pattern)
  (labels ((rec (pattern)
             (mapcar (lambda (x)
                       (if (atom x)
                           (pop source)
                           (rec x)))
               pattern)))
    (rec pattern)))

Test:

CL-USER> (mimicry '(1 2 3 4 5) '(t (u (v)) (w x)))
(1 (2 (3)) (4 5))


来源:https://stackoverflow.com/questions/24716663/how-to-convert-a-flat-list-into-a-nested-tree-like-structure

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!