簡體   English   中英

Lisp反轉所有連續的元素序列

[英]Lisp reversing all continuous sequences of elements

我想只反轉連續序列,而不是原始列表中的所有元素。

 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))

我正在考慮將它分成兩個函數:一個用於反轉一個簡單列表(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 ...

已經有了一個公認的答案 ,但這似乎是一個有趣的挑戰。 我試圖稍微抽象一些細節,並生成一個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-ish方法

(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'(bcd))返回(abcd) list **雖然可以采用更多的參數(例如,**(list *'a'b'(cde))返回(abcde) ),並且在我看來,它產生了列表的意圖(而不是任意的細胞)有點清楚。 第二,另一個答案解釋了使用destructuring-bind ; 如果這種方法可能會稍微縮短一點

(let* ((parts (reverse-until list '()))
       (head (first parts))
       (tail (second parts)))

被替換為

(destructuring-bind (head tail) (reverse-until list '())

您可以使用單個遞歸函數一次執行所有操作,通常警告您應該優先於遞歸方法的循環結構(請參閱下文):

(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))))))))

Exemples

(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))))))

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM