【问题标题】:Lisp reversing all continuous sequences of elementsLisp反转所有连续的元素序列
【发布时间】:2015-11-27 00:24:40
【问题描述】:

我只想反转连续序列,而不是原始列表中的所有元素。

 Ex:
    (reverseC '( 1 2 ( 4 5 ) 5 ) ) => ( 2 1 ( 5 4 ) 5 )
    (reverseC '(1 4 2 (3 4) 9 6 (7 8)))) => (2 4 1 (4 3) 6 9 (8 7))

我正在考虑将其拆分为 2 个函数:一个用于反转简单列表 (1 2 3) -> (3 2 1) 和一个函数 (main) 确定连续序列,从中列出一个列表,在该列表上应用反向并重新制作整个反向列表。

(defun reverse-list ( lista ) 
    (if (eql lista () )
        ()
        (append (reverse-list (cdr lista )) (list ( car lista)))
    )
)

这是反向功能,但我不知道如何做另一个。我是 Lisp 的新手,我来自 Prolog,所以情况发生了很大的变化。欢迎任何想法。

(defun reverse-more (L)
    (if (eql L nil)
        nil
        (let ( el (car L)) (aux (cdr L)))
        (if (eql (listp el) nil)
     ...No idea on the rest of the code ...

【问题讨论】:

  • 请参阅thisthis 以及this 关于反向的问题。现在,当你反转一个列表时,如果你正在访问的当前元素是一个列表,你想先递归地反转它。
  • 没有一个链接真的对我有太大帮助。我已经说过我具有反转简单列表的功能。看我的例子,它只存储连续序列。我不知道如何停止我的功能,扭转我已经阅读的内容并继续前进
  • 请详细说明“连续”是什么意思?我会理解为'(1 2 4) 将变为'(2 1 4),接受的答案并不满足。或许 contiguous/consecutive 是更好的术语?
  • 连续,我的意思是在同一级别上,而不是被另一个子列表插入。举这样一个例子是我的错误。很可能是 (1 3 (8 3) 2 ) => (3 1 (3 8) 2)。

标签: list lisp common-lisp


【解决方案1】:

已经有一个accepted answer,但这似乎是一个有趣的挑战。我尝试稍微抽象一些细节,并生成了一个 ma​​p-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 '())

【讨论】:

  • 据我了解,问题(或其措辞)只有您的回答才能解决问题。 +1
  • @DanielJour 我认为它有潜力,但我仍然不确定连续/连续的确切含义。在评论中,您提到1 2 4 变为2 1 4,因为1 2 被反转,然后4 被反转。我展示的示例在使用 '4 2 1,但传入一个检查紧邻数字的谓词很容易。
  • 抱歉造成误会。通过连续,我的意思是在同一级别上,而不是被另一个子列表干扰。举这样一个例子是我的错误。很可能是 (1 3 (8 3) 2 ) => (3 1 (3 8) 2)。我是 Lisp 的初学者,来自 Prolog,即使接受的答案也无法为我解决,因为我不知道 --typecase-- 和 --destructuring-bind-- 是如何工作的。对于初学者,我需要更多的东西,但我觉得这是最接近的东西。虽然你在这个 xD 中发现了一个巨大的挑战
  • @melye77 即使接受的答案也不能为我解决问题,因为我不知道 --typecase-- 和 --destructuring-bind-- 是如何工作的。 甚至尽管它可以满足您的要求,但您可能暂时不接受该答案。在任何时候都没有义务接受答案,过早接受可能会阻止其他人发布答案(这实际上可能更适合您的需求)。
  • @Melye77 我添加了一些关于解构绑定的解释。
【解决方案2】:

您可以使用单个递归函数一次执行所有操作,但通常会警告您应该更喜欢循环构造而不是递归方法(见下文):

(defun reverse-consecutive (list &optional acc)
  (etypecase list

    ;; BASE CASE
    ;; return accumulated list
    (null acc)

    ;; GENERAL CASE
    (cons (destructuring-bind (head . tail) list
            (typecase head
              (list
               ;; HEAD is a list:
               ;;
               ;; - stop accumulating values
               ;; - reverse HEAD recursively (LH)
               ;; - reverse TAIL recursively (LT)
               ;;
               ;; Result is `(,@ACC ,LH ,@LT)
               ;;
               (nconc acc
                      (list (reverse-consecutive head))
                      (reverse-consecutive tail)))

              ;; HEAD is not a list
              ;;
              ;; - recurse for the result on TAIL with HEAD
              ;;   in front of ACC
              ;;
              (t (reverse-consecutive tail (cons head acc))))))))

示例

(reverse-consecutive '(1 2 (3 4) 5 6 (7 8)))
=> (2 1 (4 3) 6 5 (8 7))

(mapcar #'reverse-consecutive
        '((1 3 (8 3) 2 )
          (1 4 2 (3 4) 9 6 (7 8))
          (1 2 (4 5) 5)))

=> ((3 1 (3 8) 2)
    (2 4 1 (4 3) 6 9 (8 7))
    (2 1 (5 4) 5))

备注

@Melye77 destructuring-bind 表达式的作用与 Prolog 中的 [Head|Tail] = List 相同。我本来可以写这个的

(let ((head (first list)) 
      (tail (rest list)))
 ...)

同样,我更喜欢使用(e)typecase 而不是通用的cond 表达式,因为我认为它更精确。

我本来可以写的:

(if acc
    (if (listp (first list))
      (nconc ...)
      (reverse-consecutive ...))
    acc)

...但我认为它不太清楚,教初学者也不是一件好事。 相反,我认为介绍所有可用的结构是有用的,甚至(尤其是)对于初学者来说。 例如,实际上不建议过度使用递归函数:有大量现有的序列迭代构造不依赖于尾调用优化的可用性(虽然不能保证实现,但通常可以通过适当的声明获得) .

迭代版本

这是一个使用标准reversenreverse 函数的迭代版本。与上述方法相反,内部列表只是简单地反转(仅在第一层深度检测到连续块):

(defun reverse-consecutive (list)
  (let (stack result)
    (dolist (e list (nreverse result))
      (typecase e
        (list
         (dolist (s stack)
           (push s result))
         (push (reverse e) result)
         (setf stack nil))
        (t (push e stack))))))

【讨论】:

  • 据我所知,只有连续序列应该被反转,但您的代码反转所有序列?
  • 连续,我的意思是在同一级别上,而不是被另一个子列表插入。举这样一个例子是我的错误。很可能是 (1 3 (8 3) 2 ) => (3 1 (3 8) 2)。
  • @Melye77 请查看其他测试用例以确认该功能是否符合要求。谢谢。
  • 是的,这确实是我想要的。也谢谢你的解释,现在有道理了
猜你喜欢
  • 1970-01-01
  • 2019-04-08
  • 2012-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-24
  • 1970-01-01
相关资源
最近更新 更多