简体   繁体   中英

Recursion on a list in Scheme - avoid premature termination

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!

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 (eg 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.

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

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

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 '(ab ...) '(xy ...)) returns a list (ab ... xy ...) , with the elements of (ab ...) . 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 '(ab ... mn) '(xy ...)) returns (nm ... baxy ...) . 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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM