Recursion on a list in Scheme - avoid premature termination

无人久伴 提交于 2020-01-25 11:57:27

问题


I was doing a problem from the HTDP book where you have to create a function that finds all the permutations for the list. The book gives the main function, and the question asks for you to create the helper function that would insert an element everywhere in the list. The helper function, called insert_everywhere, is only given 2 parameters.

No matter how hard I try, I can't seem to create this function using only two parameters.

This is my code:

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

My desired output for (insert_everywhere 'a (list 1 2 3)) is (list 'a 1 2 3 1 'a 2 3 1 2 'a 3 1 2 3 'a), but instead my list keeps terminating.

I've been able to create this function using a 3rd parameter "position" where I do recursion on that parameter, but that botches my main function. Is there anyway to create this helper function with only two parameters? Thanks!


回答1:


Have you tried:

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

The insert function allows you to insert values anywhere within a list:

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

The range function allows you to create Haskell-style list ranges:

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

The insert-everywhere function makes use of insert and range. It's pretty easy to understand how it works. If your implementation of scheme doesn't have the fold-right function (e.g. mzscheme) then you can define it as follows:

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

As the name implies the fold-right function folds a list from the right.




回答2:


You can do this by simply having 2 lists (head and tail) and sliding elements from one to the other:

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

In Racket, you could also express it in a quite concise way as follows:

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


This has a lot in common with my answer to Insert-everywhere procedure. There's a procedure that seems a bit odd until you need it, and then it's incredibly useful, called revappend. (append '(a b ...) '(x y ...)) returns a list (a b ... x y ...), with the elements of (a b ...). Since it's so easy to collect lists in reverse order while traversing a list recursively, it's useful sometimes to have revappend, which reverses the first argument, so that (revappend '(a b ... m n) '(x y ...)) returns (n m ... b a x y ...). revappend is easy to implement efficiently:

(define (revappend list tail)
  (if (null? list)
      tail
      (revappend (rest list)
                 (list* (first list) tail))))

Now, a direct version of this insert-everywhere is straightforward. This version isn't tail recursive, but it's pretty simple, and doesn't do any unnecessary list copying. The idea is that we walk down the lst to end up with the following rhead and 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 ...)

If you put the recursive call in the place of the ..., then you get the result that you want:

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

Now, this isn't tail recursive. If you want a tail recursive (and thus iterative) version, you'll have to construct your result in a slightly backwards way, and then reverse everything at the end. You can do this, but it does mean one extra copy of the list (unless you destructively reverse it).

(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)))))))
> (insert-everywhere 'a '(1 2 3))
'(a 1 2 3 1 a 2 3 1 2 a 3 1 2 3 a)



回答4:


How about creating a helper function to the helper function?

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

We need our sublists kept separate, so that each one can be prefixed separately. If we'd append all prematurely, we'd lose the boundaries. So we append only once, in the very end:

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

Testing:

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

By the way this internal function is tail recursive modulo cons, more or less, as also seen in the illustration. This suggests it should be possible to convert it into an iterative form. Joshua Taylor shows another way, using revappend. Reversing the list upfront simplifies the flow in his solution (which now corresponds to building directly the result row in the illustration, from right to left, instead of "by columns" in my version):

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


来源:https://stackoverflow.com/questions/20309401/recursion-on-a-list-in-scheme-avoid-premature-termination

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!