If you are looking for a solution with a recursive tail, one way is to use the let name:
(define (group n xs) (let loop ([grouped '()] [xs xs]) (if (empty? xs) (reverse grouped) (loop (cons (take xs n) grouped) (drop xs n)))))
However, this fails if xs has the remaining elements, so we need to add a case that checks this:
(define (group n xs) (let loop ([grouped '()] [xs xs]) (cond [(empty? xs) (reverse grouped)] [(<= (length xs) n) (loop (cons xs grouped) '())] [else (loop (cons (take xs n) grouped) (drop xs n))])))
It works, but we can do better. The problem is that the calculation (length xs) is done in linear time, because the only way to find the length of a simple list is the whole list. Since this is inside a loop that executes several times in proportion to the size of xs , this code works in quadratic time, when it just needs to be executed in linear time. We can get around this problem by calculating the length xs once, and then subtracting n from it every iteration:
(define (group n xs) (let loop ([grouped '()] [xs xs] [l (length xs)]) (cond [(empty? xs) (reverse grouped)] [(<= ln) (loop (cons xs grouped) '() 0)] [else (loop (cons (take xs n) grouped) (drop xs n) (- ln))])))
And we will return to linear time, while maintaining tail recursion and, therefore, constant space. However, there is another improvement. The Racket split-at function combines the take and drop functions and returns both lists as two values. To use functions with multiple values, we can use let-values :
(define (group n xs) (let loop ([grouped '()] [xs xs] [l (length xs]) (cond [(empty? xs) (reverse grouped)] [(<= ln) (loop (cons xs grouped) '() 0)] [else (let-values ([(taken dropped) (split-at xs n)]) (loop (cons taken grouped) dropped (- ln)))])))
This is a little faster because split-at can avoid repeated work by ignoring the first n elements in drop its functionality, since these elements will already be consumed by take . However, this code does not account for bad user input. If the user supplies n more than the length xs , he throws an error when he should return (list xs) . It's simple enough to check, but our code is terribly expanding to the right with all this socket. In addition to checking for this, we can split the loop into an internally defined function:
(define (group n xs) (define (loop grouped xs l) (cond [(empty? xs) (reverse grouped)] [(<= ln) (loop (cons xs grouped) '() 0)] [else (let-values ([(taken dropped) (split-at xs n)]) (loop (cons taken grouped) dropped (- ln)))])) (let ([l (length xs)]) (if (>= nl) (list xs) (loop '() xs l))))
This function is tail recursive, does not calculate (length xs) more than once, ensures that (group 4 '(1 2 3)) evaluates to '((1 2 3)) , and an uneven grouping such that (group 2 '(1 2 3) is evaluated to '((1 2) (3)) , working in linear time and constant space.