【问题标题】:How to recursively reverse a list using only basic operations?如何仅使用基本操作递归反转列表?
【发布时间】:2013-11-01 00:42:16
【问题描述】:

我想知道如何仅使用 cons、first、rest、empty 等基本操作来反转列表。

不允许使用辅助函数或累加器,该函数只接受一个输入 - 一个列表。

有人告诉我这是可能的,但我无法理解它。

这是我到目前为止所概念化的。我不知道如何为列表的其余部分形成递归。

(defunc rev-list (x)
  (if (or (equal (len x) 0) (equal (len x) 1))
      x
      (cons (first (rev-list (rest x)))
            ???)))

显然,可以使用交换列表的第一个和最后一个的函数来做类似的事情,尽管我也不完全理解它。这是它的代码:

(define swap-ends (x)
  (if (or (equal (len x) 0) (equal (len x) 1))
      x
      (cons (first (swap-ends (rest x))) 
            (swap-ends (cons (first x) 
                             (rest (swap-ends (rest x))))))))

【问题讨论】:

  • car and cdr 的概念可能对您有所帮助。
  • 嗯,我知道如何使用 first(car) 和 rest(cdr),但我不明白如何为列表的递归构建正确的列表。

标签: list recursion scheme lisp


【解决方案1】:

您的环境中有last 和butlast 吗?如果是这样,程序可以这样定义(尽管 Oscar 指出这不是您通常希望解决问题的方式):

(define (rev lst)
  (if (null? lst)
      '()
      (cons (car (last lst))
            (rev (butlast lst)))))

这里是last 和butlast 的定义。如果它们不是您的默认环境的一部分,听起来它们对您的任务没有任何好处,但是当您开始时,最好通读并考虑大量递归过程。

(define (butlast lst)
  (if (or (null? lst) (null? (cdr lst)))
      '()
      (cons (car lst) (butlast (cdr lst)))))

(define (last lst)
  (if (or (null? lst) (null? (cdr lst)))
      lst
      (last (cdr lst))))

【讨论】:

  • 我没有,这让事情变得更加困难。有一种方法可以在没有实际命令的情况下获取列表的最后一个,它是调用 (first (rev-list (rest x))),假设基本情况与我在示例中的情况一样。
  • 您对程序有书面要求吗?这可能有助于我们逐字阅读。
  • 仅使用 if、cons、first、rest、empty? (endp) 或 len,编写一个接收列表(仅输入)并将其反转的函数。不允许使用辅助函数。使用这些限制为我提供了交换端解决方案,这在概念上是相似的。我敢肯定,如果我了解它是如何工作的,我就可以为反向列表做类似的事情。
【解决方案2】:
(define (reverse x)
  (let loop ((x x) (y '()))
    (if (null? x)
        y
        (let ((temp (cdr x)))
          (set-cdr! x y)
          (loop temp x))))))

确实是少数几种有效地做到这一点的方法之一。但仍然是一个辅助程序。

其他方式,但不是尾递归,并且如果追加不使用 set-cdr!对于大型列表,它确实无法使用。

(define (reverse L)
  (if (null? l)
      '()
       (append (reverse (cdr L)) (list (car L)))))

【讨论】:

  • 在之前(现已删除)的回答中,OP 表示他也不能使用 append。你的第一个变体使用了一个助手,就像我自己的第二个变体一样。我认为不可能严格遵守 OP 的所有限制,很可能是他误解了问题描述
【解决方案3】:

(注意:答案在本文底部)第二个功能,

(define (swap-ends x)                                   ; swap [] = []
  (if (or (equal (length x) 0) (equal (length x) 1))    ; swap [x] = [x]
      x                                                 ; swap (x:xs) 
      (cons (first (swap-ends (rest x)))                ;    | (a:b) <- swap xs 
            (swap-ends (cons (first x)                  ;    = a : swap (x : b)
                             (rest (swap-ends (rest x))))))))

(在 cmets 中使用 Haskell 翻译)你问它有什么作用? if的替代子句的数据流图是

                   /-> first ----------------------> cons
x --> first ------/-------------> cons --> swap --/
  \-> rest -> swap ---> rest ---/

(按照从左到右的箭头)。所以,

[] -> []
[1] -> [1]
                     /-> 2 -----------------------> [2,1]
[1,2] --> 1 --------/------------> [1] --> [1] --/
      \-> [2] -> [2] ---> [] ---/

                           /-> 3 -------------------------> [3,2,1]
[1,2,3] --> 1 ------------/----------> [1,2] --> [2,1] --/
        \-> [2,3] -> [3,2] -> [2] --/

                             /-----> 4 ----------------------------> [4,2,3,1]
[1,2,3,4] --> 1 ------------/---------------> [1,3,2] -> [2,3,1] -/
          \-> [2,3,4] -> [4,3,2] -> [3,2] -/

到目前为止,它确实交换了列表的结尾元素。让我们通过自然归纳来证明,

true(N-1) =&gt; true(N):

                       /-> N --------------------------------------> [N,2..N-1,1]
[1..N] --> 1 ---------/-----------> [1,3..N-1,2] -> [2,3..N-1,1] -/
       \-> [2..N] -> [N,3..N-1,2]   /
                    -> [3..N-1,2] -/

所以它被证明了。因此,我们需要设计一个数据流图,在反转(N-1)长度列表的假设下,将反转N长度列表:

[1..N] --> 1 ------------------------------------\
       \-> [2..N] -> [N,N-1..2] -> N -------------\------------------\
                     \-> [N-1,N-2..2] -> [2..N-1] -> [1..N-1] -> rev -> cons

这给了我们实现

(define (rev ls)                                 ; rev [] = []
  (cond                                          ; rev [x] = [x]
    ((null? ls) ls)                              ; rev (x:xs) 
    ((null? (rest ls)) ls)                       ;   | (a:b) <- rev xs 
    (else                                        ;   = a : rev (x : rev b)
      (cons (first (rev (rest ls)))
            (rev (cons (first ls)
                       (rev (rest (rev (rest ls))))))))))

(rev '(1 2 3 4 5))     ; testing
;Value 13: (5 4 3 2 1)

cmets 中的 Haskell 翻译很自然地遵循图表。它实际上是可读:a 是最后一个元素,b 是反转的“核心”(即没有第一个和最后一个元素的输入列表),所以我们反转反转的核心,前置获取输入列表的 butlast 部分的第一个元素,然后将其反转并添加最后一个元素。 简单。 :)

2020 年更新:这是一个基于 @Rörd 来自 the comments 的 code 的 Scheme 版本,因此它具有类似的可读性,使用解构参数代替 Haskell 的模式匹配:

(define (bind lst fun)
  (apply fun lst))

(define (rev lst)
  (if (or (null? lst)
          (null? (cdr lst)))
    lst
    (bind lst
      (lambda (first . rest)
         (bind (rev rest)
           (lambda (last . revd-core)
              (cons last (rev (cons first (rev revd-core))))))))))

【讨论】:

猜你喜欢
  • 2014-04-13
  • 2021-12-11
  • 2020-12-04
  • 2010-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-22
相关资源
最近更新 更多