一个不同的列表合并到Lisp中的新列表中

时间:2017-09-10 16:47:57

标签: lisp common-lisp

我想问一下如何将两个不同的数字列表合并到一个新的列表中,在Common Lisp中保持它们之间的“共同点”。

实施例

list1:(1 2 3 2 2)
清单2:(1/2 1/2 1 2 2 1 2 1)
结果:(1/2 1/2 1 1 1 2 1 1 1 1)

我希望下面的图片可以准确描述问题。 列表是数字,因为它必须比较两个系列的不同单位,并进一步将两个系列的每个数字的起点分成新的系列。

Image_1. I think this image is the best way to describe the problem.

2 个答案:

答案 0 :(得分:0)

根据你的描述,我写了两个相互递归的函数MRG和SPLIT:

  • MRG遍历第一个列表,所有调用每个元素的SPLIT
  • SPLIT尝试从第二个列表中收集足够的元素,其总和等于第一个列表中的当前元素。如果第二个列表中的元素太大,则将其拆分,并将剩余的元素重新注入第二个列表。 SPLIT在完成处理第一个列表中的当前元素时也会调用MRG。

以下是显示如何计算结果的执行跟踪。

0: (MRG (1 2 3 2 2) (1/2 1/2 1 2 2 1 2 1))
    1: (SPLIT 1 (1/2 1/2 1 2 2 1 2 1) (2 3 2 2))
      2: (SPLIT 1/2 (1/2 1 2 2 1 2 1) (2 3 2 2))
        3: (SPLIT 0 (1 2 2 1 2 1) (2 3 2 2))
          4: (MRG (2 3 2 2) (1 2 2 1 2 1))
            5: (SPLIT 2 (1 2 2 1 2 1) (3 2 2))
              6: (SPLIT 1 (2 2 1 2 1) (3 2 2))
                7: (SPLIT 0 (1 2 1 2 1) (3 2 2))
                  8: (MRG (3 2 2) (1 2 1 2 1))
                    9: (SPLIT 3 (1 2 1 2 1) (2 2))
                      10: (SPLIT 2 (2 1 2 1) (2 2))
                        11: (SPLIT 0 (1 2 1) (2 2))
                          12: (MRG (2 2) (1 2 1))
                            13: (SPLIT 2 (1 2 1) (2))
                              14: (SPLIT 1 (2 1) (2))
                                15: (SPLIT 0 (1 1) (2))
                                  16: (MRG (2) (1 1))
                                    17: (SPLIT 2 (1 1) NIL)
                                      18: (SPLIT 1 (1) NIL)
                                        19: (SPLIT 0 NIL NIL)
                                            20: (MRG NIL NIL)
                                            20: MRG returned NIL
                                        19: SPLIT returned NIL
                                      18: SPLIT returned (1)
                                    17: SPLIT returned (1 1)
                                  16: MRG returned (1 1)
                                15: SPLIT returned (1 1)
                              14: SPLIT returned (1 1 1)
                            13: SPLIT returned (1 1 1 1)
                          12: MRG returned (1 1 1 1)
                        11: SPLIT returned (1 1 1 1)
                      10: SPLIT returned (2 1 1 1 1)
                    9: SPLIT returned (1 2 1 1 1 1)
                  8: MRG returned (1 2 1 1 1 1)
                7: SPLIT returned (1 2 1 1 1 1)
              6: SPLIT returned (1 1 2 1 1 1 1)
            5: SPLIT returned (1 1 1 2 1 1 1 1)
          4: MRG returned (1 1 1 2 1 1 1 1)
        3: SPLIT returned (1 1 1 2 1 1 1 1)
      2: SPLIT returned (1/2 1 1 1 2 1 1 1 1)
    1: SPLIT returned (1/2 1/2 1 1 1 2 1 1 1 1)
0: MRG returned (1/2 1/2 1 1 1 2 1 1 1 1)

我没有尝试优化代码,我只是尝试以能够产生有用跟踪的方式正常工作。但这看起来像循环也可能起作用。

迭代版本(编辑)

这是一个没有递归和调试语句的版本:

(defun mrg% (lx ly)
  (with-list-collector (collect)
    (flet ((collect (v)
             "Add print statements to COLLECT"
             (print (list :collect v))
             (collect v)))
      (dolist (x lx)
        (loop
          (print (list :split x ly))
          (unless (plusp x)
            (return))
          (assert ly)
          (let ((y (pop ly)))
            (if (<= y x)
                (decf x (collect y))
                (return (push (- y (collect x)) ly)))))))))

用你的例子:

(mrg% '(1 2 3 2 2 )
      '(1/2 1/2 1 2 2 1 2 1))

...打印:

(:SPLIT 1 (1/2 1/2 1 2 2 1 2 1)) 
(:COLLECT 1/2) 
(:SPLIT 1/2 (1/2 1 2 2 1 2 1)) 
(:COLLECT 1/2) 
(:SPLIT 0 (1 2 2 1 2 1)) 
(:SPLIT 2 (1 2 2 1 2 1)) 
(:COLLECT 1) 
(:SPLIT 1 (2 2 1 2 1)) 
(:COLLECT 1) 
(:SPLIT 3 (1 2 1 2 1)) 
(:COLLECT 1) 
(:SPLIT 2 (2 1 2 1)) 
(:COLLECT 2) 
(:SPLIT 0 (1 2 1)) 
(:SPLIT 2 (1 2 1)) 
(:COLLECT 1) 
(:SPLIT 1 (2 1)) 
(:COLLECT 1) 
(:SPLIT 2 (1 1)) 
(:COLLECT 1) 
(:SPLIT 1 (1)) 
(:COLLECT 1) 
(:SPLIT 0 NIL)

为了完整性,这是我正在使用的宏:

(defmacro with-list-collector 
  ((collector-name &optional name copy-p) &body body)
  "Bind COLLECTOR-NAME as a local function to collect items in a list.

A call to (COLLECTOR-NAME VALUE) accumulates VALUE into a list, in the
same order as the calls are being made. The resulting list can be
accessed through the symbol NAME, if given, or as the return value of
WITH-LIST-COLLECTOR. 

The return value of (COLLECTOR-NAME VALUE) is VALUE.

If COPY-P is T, each access to NAME performs a copy of the list under
construction. Otherwise, NAME refers to a list which last cons-cell is
modified after each call to COLLECTOR-NAME (except if it is NIL).

The return value of the whole form is the list being built, ONLY when
NAME is NIL. Otherwise, the return value is given by the last form of
BODY: it is assumed that the list will be accessed by NAME if
necessary, and that the interesting value is given by BODY."
  (assert (or (not copy-p) name) ()
          "A COPY argument is only valid when a NAME is given.")
  (alexandria:with-gensyms (queue head value)
    (let ((flet-expr `(flet ((,collector-name (,value)
                               (prog1 ,value
                                 (setf ,queue
                                       (setf (cdr ,queue)
                                             (cons ,value nil))))))
                        (declare (inline ,collector-name))
                        ,@body)))
      `(let* ((,queue (cons nil nil))
              (,head ,queue))
         ,(if name
              `(symbol-macrolet
                   ((,name ,(if copy-p
                                `(copy-seq (cdr ,head))
                                `(cdr ,head))))
                 ,flet-expr)
              ;; anonymous list : return as result
              `(progn ,flet-expr
                      (cdr ,head)))))))

答案 1 :(得分:0)

在我看来,列表元素就像是节拍之间的暂停。我的算法会在每一步查找最小暂停,然后减少剩余的当前暂停,并在当前暂停为零时推进列表。

为了说明,我将一条打印指令放入循环中:

(defun merge-beats (&rest lists)
  (do* ((minpause nil (reduce #'min (mapcar #'first pauses)))
        (result () (cons minpause result))
        (pauses lists
                (remove nil
                        (mapcar (lambda (pause-list)
                                  (let ((current-pause (- (first pause-list)
                                                          minpause)))
                                    (if (zerop current-pause)
                                        (rest pause-list)
                                        (cons current-pause
                                              (rest pause-list)))))
                                pauses)))
        (- #1=(print (list :minpause minpause :result result :pauses pauses))
           #1#))
       ((endp pauses) (nreverse result))))


CL-USER> (merge-beats '(1 2 3 2 2)
                      '(1/2 1/2 1 2 2 1 2 1))

(:MINPAUSE NIL :RESULT NIL :PAUSES ((1 2 3 2 2) (1/2 1/2 1 2 2 1 2 1))) 
(:MINPAUSE 1/2 :RESULT (1/2) :PAUSES ((1/2 2 3 2 2) (1/2 1 2 2 1 2 1))) 
(:MINPAUSE 1/2 :RESULT (1/2 1/2) :PAUSES ((2 3 2 2) (1 2 2 1 2 1))) 
(:MINPAUSE 1 :RESULT (1 1/2 1/2) :PAUSES ((1 3 2 2) (2 2 1 2 1))) 
(:MINPAUSE 1 :RESULT (1 1 1/2 1/2) :PAUSES ((3 2 2) (1 2 1 2 1))) 
(:MINPAUSE 1 :RESULT (1 1 1 1/2 1/2) :PAUSES ((2 2 2) (2 1 2 1))) 
(:MINPAUSE 2 :RESULT (2 1 1 1 1/2 1/2) :PAUSES ((2 2) (1 2 1))) 
(:MINPAUSE 1 :RESULT (1 2 1 1 1 1/2 1/2) :PAUSES ((1 2) (2 1))) 
(:MINPAUSE 1 :RESULT (1 1 2 1 1 1 1/2 1/2) :PAUSES ((2) (1 1))) 
(:MINPAUSE 1 :RESULT (1 1 1 2 1 1 1 1/2 1/2) :PAUSES ((1) (1))) 
(:MINPAUSE 1 :RESULT (1 1 1 1 2 1 1 1 1/2 1/2) :PAUSES NIL) 
(1/2 1/2 1 1 1 2 1 1 1 1)
CL-USER>