Scheme中列表的递归 - 避免提前终止

时间:2013-12-01 06:25:55

标签: recursion scheme

我在HTDP一书中遇到了一个问题,你必须创建一个找到列表所有排列的函数。本书给出了main函数,并且问题要求您创建辅助函数,该函数将在列表中的任何位置插入元素。称为insert_everywhere的辅助函数只有2个参数。

无论我怎么努力,我似乎​​无法仅使用两个参数创建此功能。

这是我的代码:

(define (insert_everywhere elt lst)
  (cond
    [(empty? lst) empty]
    [else (append (cons elt lst) 
                  (cons (first lst) (insert_everywhere elt (rest lst))))]))

(insert_everywhere 'a (list 1 2 3))的所需输出为(list 'a 1 2 3 1 'a 2 3 1 2 'a 3 1 2 3 'a),但我的列表会一直停止。

我已经能够使用第三个参数“position”来创建这个函数,在那里我对该参数进行递归,但这会破坏我的主要功能。反正只有两个参数来创建这个辅助函数吗?谢谢!

4 个答案:

答案 0 :(得分:1)

你试过了吗?

(define (insert x index xs)
    (cond ((= index 0) (cons x xs))
          (else (cons (car xs) (insert x (- index 1) (cdr xs))))))

(define (range from to)
    (cond ((> from to) empty)
          (else (cons from (range (+ from 1) to)))))

(define (insert-everywhere x xs)
    (fold-right (lambda (index ys) (append (insert x index xs) ys))
        empty (range 0 (length xs))))

insert功能允许您在列表中的任何位置插入值:

(insert 'a 0 '(1 2 3)) => (a 1 2 3)
(insert 'a 1 '(1 2 3)) => (1 a 2 3)
(insert 'a 2 '(1 2 3)) => (1 2 a 3)
(insert 'a 3 '(1 2 3)) => (1 2 3 a)

range函数允许您创建Haskell样式的列表范围:

(range 0 3) => (0 1 2 3)

insert-everywhere功能使用insertrange。很容易理解它是如何工作的。如果您的方案实施没有fold-right函数(例如mzscheme),那么您可以按如下方式定义:

(define (fold-right f acc xs)
    (cond ((empty? xs) acc)
          (else (f (car xs) (fold-right f acc (cdr xs))))))

顾名思义,fold-right函数从右侧折叠列表。

答案 1 :(得分:1)

如何为辅助函数创建辅助函数?

(define (insert_everywhere elt lst)
    (define (insert_everywhere_aux elt lst)
      (cons (cons elt lst)
            (if (empty? lst)
                empty
                (map (lambda (x) (cons (first lst) x))
                     (insert_everywhere_aux elt (rest lst))))))
    (apply append (insert_everywhere_aux elt lst)))

我们需要将我们的子列表分开,以便每个子列表可以单独添加前缀。如果我们过早地追加所有,我们就会失去界限。所以我们最后只追加一次:

insert a (list 1 2 3) =                             ; step-by-step illustration:
                                            ((a))   ; the base case;
                              ((a/ 3)/    (3/ a))   ; '/' signifies the consing
                 ((a/ 2 3)/  (2/ a 3)   (2/ 3 a))
   ((a/ 1 2 3)/ (1/ a 2 3) (1/ 2 a 3) (1/ 2 3 a))
   ( a  1 2 3    1  a 2 3   1  2 a 3   1  2 3 a )   ; the result

测试:

(insert_everywhere 'a (list 1 2 3))
;Value 19: (a 1 2 3 1 a 2 3 1 2 a 3 1 2 3 a)

顺便说一下,这个内部函数是tail recursive modulo cons,或多或少,如图所示。这表明应该可以将其转换为迭代形式。 Joshua Taylor shows另一种方式,使用revappend。预先反转列表简化了他的解决方案中的流程(现在对应于直接构建插图中的结果行,从右到左,而不是在我的版本中“按列”):

(define (insert_everywhere elt lst)
  (let g ((rev (reverse lst)) 
          (q   '())
          (res '()))
    (if (null? rev) 
      (cons elt (append q res))
      (g (cdr rev) 
         (cons (car rev) q) 
         (revappend rev (cons elt (append q res)))))))

答案 2 :(得分:1)

你可以通过简单地将2个列表(头部和尾部)和滑动元素从一个到另一个来完成:

(define (insert-everywhere elt lst)
  (let loop ((head null) (tail lst))      ; initialize head (empty), tail (lst)
    (append (append head (cons elt tail)) ; insert elt between head and tail
            (if (null? tail)
                null                      ; done
                (loop (append head (list (car tail))) (cdr tail)))))) ; slide


(insert-everywhere 'a (list 1 2 3))
=> '(a 1 2 3 1 a 2 3 1 2 a 3 1 2 3 a)

在Racket中,你也可以用一种非常简洁的方式表达如下:

(define (insert-everywhere elt lst)
  (for/fold ((res null)) ((i (in-range (add1 (length lst)))))
    (append res (take lst i) (cons elt (drop lst i)))))

答案 3 :(得分:1)

这与my answerInsert-everywhere procedure有许多共同之处。有一个程序看起来有点奇怪,直到你需要它,然后它非常有用,称为revappend(append '(a b ...) '(x y ...))返回列表(a b ... x y ...),其中包含(a b ...)元素。由于以递归方式遍历列表时以 reverse 顺序收集列表非常容易,因此有时候revappend有用,其中会反转第一个参数,以便{ {1}}返回(revappend '(a b ... m n) '(x y ...))(n m ... b a x y ...)很容易有效实施:

revappend

现在,这个(define (revappend list tail) (if (null? list) tail (revappend (rest list) (list* (first list) tail)))) 的直接版本很简单。这个版本不是尾递归的,但它非常简单,并且不会进行任何不必要的列表复制。我们的想法是,我们沿着insert-everywhere向下走,最后得到以下lstrhead

tail

如果您将递归调用放在rhead tail (revappend rhead (list* item (append tail ...))) ------- ------- ------------------------------------------------ () (1 2 3) (r 1 2 3 ...) (1) (2 3) (1 r 2 3 ...) (2 1) (3) (1 2 r 3 ...) (3 2 1) () (1 2 3 r ...) 的位置,那么您将获得所需的结果:

...
(define (insert-everywhere item lst)
  (let ie ((rhead '())
           (tail lst))
    (if (null? tail)
        (revappend rhead (list item))
        (revappend rhead
                   (list* item 
                          (append tail
                                  (ie (list* (first tail) rhead)
                                      (rest tail))))))))

现在,这不是尾递归。如果你想要一个尾递归(以及迭代)版本,你必须以稍微向后的方式构造你的结果,然后在最后反转所有内容。你可以这样做,但它确实意味着列表的一个额外副本(除非你破坏性地反转它)。

> (insert-everywhere 'a '(1 2 3))
'(a 1 2 3 1 a 2 3 1 2 a 3 1 2 3 a)
(define (insert-everywhere item lst)
  (let ie ((rhead '())
           (tail lst)
           (result '()))
    (if (null? tail)
        (reverse (list* item (append rhead result)))
        (ie (list* (first tail) rhead)
            (rest tail)
            (revappend tail
                       (list* item
                              (append rhead
                                      result)))))))