已经有一个accepted answer,但这似乎是一个有趣的挑战。我尝试稍微抽象一些细节,并生成了一个 map-contig 函数,该函数使用输入列表的每个连续子列表调用一个函数,并通过谓词确定什么是连续列表这是传入的。
(defun map-contig (function predicate list)
"Returns a new list obtained by calling FUNCTION on each sublist of
LIST consisting of monotonically non-decreasing elements, as determined
by PREDICATE. FUNCTION should return a list."
;; Initialize an empty RESULT, loop until LIST is empty (we'll be
;; popping elements off of it), and finally return the reversed RESULT
;; (since we'll build it in reverse order).
(do ((result '())) ((endp list) (nreverse result))
(if (listp (first list))
;; If the first element is a list, then call MAP-CONTIG on it
;; and push the result into RESULTS.
(push (map-contig function predicate (pop list)) result)
;; Otherwise, build up sublist (in reverse order) of contiguous
;; elements. The sublist is finished when either: (i) LIST is
;; empty; (ii) another list is encountered; or (iii) the next
;; element in LIST is non-contiguous. Once the sublist is
;; complete, reverse it (since it's in reverse order), call
;; FUNCTION on it, and add the resulting elements, in reverse
;; order, to RESULTS.
(do ((sub (list (pop list)) (list* (pop list) sub)))
((or (endp list)
(listp (first list))
(not (funcall predicate (first sub) (first list))))
(setf result (nreconc (funcall function (nreverse sub)) result)))))))
这是您的原始示例:
(map-contig 'reverse '< '(1 2 (4 5) 5))
;=> (2 1 (5 4) 5)
值得注意的是,这将检测单个子列表中的不连续性。例如,如果我们只想要整数的连续序列(例如,每个连续的差都是 1),我们可以使用特殊的谓词来做到这一点:
(map-contig 'reverse (lambda (x y) (eql y (1+ x))) '(1 2 3 5 6 8 9 10))
;=> (3 2 1 6 5 10 9 8)
如果你只想在子列表出现时中断,你可以只使用一个总是返回 true 的谓词:
(map-contig 'reverse (constantly t) '(1 2 5 (4 5) 6 8 9 10))
;=> (5 2 1 (5 4) 10 9 8 6)
这是另一个例子,其中“连续”的意思是“具有相同的符号”,我们不是颠倒连续的序列,而是对它们进行排序:
;; Contiguous elements are those with the same sign (-1, 0, 1),
;; and the function to apply is SORT (with predicate <).
(map-contig (lambda (l) (sort l '<))
(lambda (x y)
(eql (signum x)
(signum y)))
'(-1 -4 -2 5 7 2 (-6 7) -2 -5))
;=> (-4 -2 -1 2 5 7 (-6 7) -5 -2)
更接近 Prolog 的方法
(defun reverse-contig (list)
(labels ((reverse-until (list accumulator)
"Returns a list of two elements. The first element is the reversed
portion of the first section of the list. The second element is the
tail of the list after the initial portion of the list. For example:
(reverse-until '(1 2 3 (4 5) 6 7 8))
;=> ((3 2 1) ((4 5) 6 7 8))"
(if (or (endp list) (listp (first list)))
(list accumulator list)
(reverse-until (rest list) (list* (first list) accumulator)))))
(cond
;; If LIST is empty, return the empty list.
((endp list) '())
;; If the first element of LIST is a list, then REVERSE-CONTIG it,
;; REVERSE-CONTIG the rest of LIST, and put them back together.
((listp (first list))
(list* (reverse-contig (first list))
(reverse-contig (rest list))))
;; Otherwise, call REVERSE-UNTIL on LIST to get the reversed
;; initial portion and the tail after it. Combine the initial
;; portion with the REVERSE-CONTIG of the tail.
(t (let* ((parts (reverse-until list '()))
(head (first parts))
(tail (second parts)))
(nconc head (reverse-contig tail)))))))
(reverse-contig '(1 2 3 (4 5) 6 7 8))
;=> (3 2 1 (5 4) 8 7 6)
(reverse-contig '(1 3 (4) 6 7 nil 8 9))
;=> (3 1 (4) 7 6 nil 9 8)
对此只有两个注释。首先,list* 非常类似于 cons,因为 (list* 'a '(b c d)) 返回 (a b c d) 。 list** 可以接受更多参数(例如,**(list* 'a 'b '(c d e)) 返回 (a b c d e)),而且,在我看来,它使列表(相对于任意 cons-cells)的意图更加清晰。其次,另一个答案解释了destructuring-bind的使用;如果
(let* ((parts (reverse-until list '()))
(head (first parts))
(tail (second parts)))
被替换为
(destructuring-bind (head tail) (reverse-until list '())