This answer is a series of translations of @rnso's code, modified to use a recursive helper function instead of repeated set!
.
#lang racket
(define (cpermit sl)
;; n starts at (length sl) and goes towards zero
;; sl starts at sl
;; outl starts at '()
(define (loop n sl outl)
(cond [(zero? n) outl]
[else
(loop (sub1 n) ; the new n
(append (rest sl) (list (first sl))) ; the new sl
(cons sl outl))])) ; the new outl
(loop (length sl) sl '()))
> (cpermit (list 1 2 3 4))
(list (list 4 1 2 3) (list 3 4 1 2) (list 2 3 4 1) (list 1 2 3 4))
For a shorthand for this kind of recursive helper, you can use a named let
. This brings the initial values up to the top to make it easier to understand.
#lang racket
(define (cpermit sl)
(let loop ([n (length sl)] ; goes towards zero
[sl sl]
[outl '()])
(cond [(zero? n) outl]
[else
(loop (sub1 n) ; the new n
(append (rest sl) (list (first sl))) ; the new sl
(cons sl outl))]))) ; the new outl
> (cpermit (list 1 2 3 4))
(list (list 4 1 2 3) (list 3 4 1 2) (list 2 3 4 1) (list 1 2 3 4))
To @rnso, you can think of the n
, sl
, and outl
as fulfilling the same purpose as "mutable variables," but this is really the same code as I wrote before when I defined loop
as a recursive helper function.
The patterns above are very common for accumulators in Scheme/Racket code. The (cond [(zero? n) ....] [else (loop (sub1 n) ....)])
is a little annoying to write out every time you want a loop like this. So instead you can use for/fold
with two accumulators.
#lang racket
(define (cpermit sl)
(define-values [_ outl]
(for/fold ([sl sl] [outl '()])
([i (length sl)])
(values (append (rest sl) (list (first sl))) ; the new sl
(cons sl outl)))) ; the new outl
outl)
> (cpermit (list 1 2 3 4))
(list (list 4 1 2 3) (list 3 4 1 2) (list 2 3 4 1) (list 1 2 3 4))
You might have noticed that the outer list has the (list 1 2 3 4)
last, the (list 2 3 4 1)
second-to-last, etc. This is because we built the list back-to-front by pre-pending to it with cons
. To fix this, we can just reverse it at the end.
#lang racket
(define (cpermit sl)
(define-values [_ outl]
(for/fold ([sl sl] [outl '()])
([i (length sl)])
(values (append (rest sl) (list (first sl))) ; the new sl
(cons sl outl)))) ; the new outl
(reverse outl))
> (cpermit (list 1 2 3 4))
(list (list 1 2 3 4) (list 2 3 4 1) (list 3 4 1 2) (list 4 1 2 3))
And finally, the (append (rest sl) (list (first sl)))
deserves to be its own helper function, because it has a clear purpose: to rotate the list once around.
#lang racket
;; rotate-once : (Listof A) -> (Listof A)
;; rotates a list once around, sending the first element to the back
(define (rotate-once lst)
(append (rest lst) (list (first lst))))
(define (cpermit sl)
(define-values [_ outl]
(for/fold ([sl sl] [outl '()])
([i (length sl)])
(values (rotate-once sl) ; the new sl
(cons sl outl)))) ; the new outl
(reverse outl))
> (cpermit (list 1 2 3 4))
(list (list 1 2 3 4) (list 2 3 4 1) (list 3 4 1 2) (list 4 1 2 3))